MyBatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
数据库连接池
传统情况下,客户端每一次请求到达服务器,服务器如果需要访问数据库的话,先要跟数据库建立连接,再进行数据库访问,然后断开数据库连接。 这个过程是比较费时费力的。数据库连接池就是在一块内存中提前放好一堆的数据库连接对象。要使用的时候直接来取出一个使用,用完之后还回连接池中,不必销毁。 这样做酒省去了建立连接和断开连接的过程,大大的提升了访问速度。
在 Maven 中开发 Mybatis
- 新建 Maven 项目,新建一个模块
- 修改目录结构,将缺失的目录补齐
- 在 pom.xml 文件中添加 MyBatis 依赖和 mysql 依赖
<dependencys>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
</dependencys>
- 在 pom.xml 文件中添加资源文件配置
<build>
<resources>
<resource>
<directory>src/java/main</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/java/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
- 在 IDEA 中添加数据库可视化
- 在 IDEA 右侧边栏中选择 Database 添加一个 MySQL 服务
- 填写数据库名称,用户名,密码建立连接
- 在资源目录中添加数据库配置文件 jdbc.properties,配置内容包括(驱动,数据库链接,用户名,密码)
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/todo?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
- 在资源目录中添加 SqlMapConfig.xml 文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 读取属性文件 jdbc.properties -->
<!--
resource:从 resources 目录下查找指定的配置文件
url:以绝对路劲的形式查找指定的属性文件(D:/dir/.../jdbc.properties)
两个属性都是用于指定配置文件的路径,使用其一即可
-->
<properties resource="jdbc.properties" />
<settings>
<!-- 设置输出日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!-- 别名设置 -->
<typeAliases>
<!--
设置 User 类别名为 user,在 mapper 中就可以不用写完整的 User 类名,可以使用 user 代替
单个注册
type:类名,包括包名,要写全
alias:别名
-->
<!--<typeAlias type="com.lmb.entity.User" alias="user" />-->
<!--
将怎整个包中的类都注册别名,默认的别名为小驼峰形式
例:com.lmb.entity.User -> user
com.lmb.entity.StudentInformation -> studentInformation
-->
<package name="com.lmb.entity"/>
</typeAliases>
<!-- 配置数据库的环境变量 -->
<!--
default:使用哪一套配置,方便在多中环境中切换,只需要修改 default 的值即可。
-->
<environments default="dev">
<!-- dev 数据库配置 -->
<environment id="dev">
<!--
配属事务管理器
type:指定事务管理的方式
- JDBC:事务的管理交给程序员
- MANAGED:事务的管理由容器(Spring)来管理
-->
<transactionManager type="JDBC" />
<!--
配置数据源
type:指定不同的配置方式
- JNDI:java 命名目录接口,在服务器端进行数据库连接池的管理
- POOLED:使用数据库连接池
- UNPOOLED:不使用数据库连接池
-->
<dataSource type="POOLED">
<!-- 指定数据库配置的属性(驱动,url,username,password) -->
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!-- pro 数据库配置 -->
<!--<environment id="pro">
<transactionManager type=""></transactionManager>
<dataSource type=""></dataSource>
</environment>-->
</environments>
<!-- 注册 mapper.xml 文件 -->
<!--
resource 和 url 属性参考上面
class:动态代理方式下的注册
-->
<mappers>
<mapper resource="UserMapper.xml" />
</mappers>
</configuration>
- 添加业务功能的 xml 文件,
- 创建实体类,用封装装数据
- 实体类的字段名和数据类型要跟数据库表字段完全一致
- 创建测试类,进行功能测试
MyBatis 编程步骤
class Test {
public static void main(String[] args) {
// 1. 使用文件流读取核心配置文件 SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建 SqlSessionFactory 工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3. 取出 SqlSession 对象
SqlSession session = factory.openSession();
// 4. 完成数据库操作,指定执行的操作:mapper 中指定的 ${namespace}.${id}
List<User> list = session.selectList("user.getAllUser");
// 5. 如果是增删改的操作需要手动提交事务,查询的时候不要提交事务。
// sqlSession.commit();
// 4. 关闭 SqlSession 对象
session.close();
}
}
动态代理的步骤
- UserMapper.xml 文件与 UserMapper.java 的接口必须同一目录下。
- UserMapper.xml 文件与 UserMapper.java 的接口文件名必须一致,除后缀名。
- UserMapper.xml 文件与 UserMapper.java 的接口中方法的名称完全一致。
- UserMapper.xml 文件中标签的 parameterType 属性值与 UserMapper.java 的接口中方法的参数值完全一致
- UserMapper.xml 文件中标签的 resultType 值与 UserMapper.java 接口文件中方法中的返回值类型完全一致
- UserMapper.xml 文件中 namespace 属性必须是接口的完全限定名称 com.lmb.mapper.UserMapper
- 在 SqlMapConfig.xml 文件中注册 mapper 文件时,使用 class=接口的完全限定名称。(class=com.lmb.mapper.UserMapper)
动态代理的 MyBatis 编程步骤
class Test {
public static void main(String[] args) {
// 1. 使用文件流读取核心配置文件 SqlMapConfig.xml
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建 SqlSessionFactory 工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3. 取出 SqlSession 对象
SqlSession session = factory.openSession();
// 4. 取出动态代理的对象,完成接口方法的调用。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 4. 完成数据库操作。调用接口中方法,实际上是执行了 MyBatis 代理功能
List<User> list = userMapper.getAll();
// 5. 如果是增删改的操作需要手动提交事务,查询的时候不要提交事务。
// sqlSession.commit();
// 4. 关闭 SqlSession 对象
session.close();
}
}
Mapper 文件中标签
-
:查询
-
:插入
-
:更新
-
:删除
-
select last_insert_id():返回主键
- keyProperty:标签内容的运算值要放到哪个属性中
- resultType:返回的类型
- order:"BEFORE"/"AFTER" 在 sql 语句执行之后/之前返回
参数
- id:指定调用的名称
- parameterType:参数的数据类型
- resultType:返回值的类型
#{} 和 ${} 的使用区别
- ${} 可以拼接到 sql 语句的关键字中,存在 sql 注入的风险。
- select {} from t_user
- #{} 一般作为未知值的占位符,不存在 sql 注入的风险。
- select name, age from t_user where id=#{}
动态 sql
- sql / include
- 定义代码片段 / 引用代码片段
<mapper>
<sql id="columns">
id, userName, password, createTime, updateTime
</sql>
<select id="getAll" resultType="user">
select <include refid="columns" />
from <include refid="table" />
</select>
</mapper>
- where
- 替代 where 关键字
<mapper>
<select>
select <include refid="columns" />
from <include refid="table" />
<where>
id = #{id}
</where>
</select>
<!--
select id, userName, password, createTime, updateTime
from t_user
where id = #{id}
-->
</mapper>
- if
- 条件判断为 true,将会把代码片段插入 sql 语句中
- attribute
- test:条件判断表达式
<!-- 按多种组合条件查询 -->
<mapper>
<select id="getByConditions" parameterType="user" resultType="user">
select <include refid="columns" />
from <include refid="table" />
<where>
<if test="userName != null and userName != ''">
and userName like concat("%", #{userName}, "%")
</if>
<if test="password != null and password != ''">
and password like concat("%", #{password}, "%")
</if>
</where>
</select>
</mapper>
- set
- 代替 update 语句中的 set 关键字
<mapper>
<update>
update t_user
<set>
<if test="userName != null and userName != ''">
userName = #{userName}
</if>
<if test="password != null and password != ''">
password = #{password}
</if>
</set>
where id = #{id}
</update>
</mapper>
- foreach
- 循环
- attribute
- collection:array/list/map 循环的数据类型(数组/集合/map)
- item:每次循环的临时变量
- separator:多个值或者对象或者sql语句之间的分隔符
- open:整个循环的前置符号
- close:整个循环的后置符号
<mapper>
<!-- 批量增加 -->
<insert id="insertBatch">
insert into t_user(userName, password, createTime, updateTime)
values
<foreach collection="list" item="user" separator=",">
(#{user.userName}, #{user.password}, #{user.createTime}, null)
</foreach>
</insert>
<!-- 批量更新 -->
<update id="updateBatch">
<foreach collection="list" item="user" separator=";">
update <include refid="table" />
<set>
<if test="user.userName != '' and user.userName != null">
userName=#{user.userName},
</if>
<if test="user.password != '' and user.password != null">
password=#{user.password},
</if>
<if test="user.createTime != '' and user.createTime != null">
createTime=#{user.createTime},
</if>
<if test="user.updateTime != '' and user.updateTime != null">
updateTime=#{user.updateTime},
</if>
</set>
where id=#{user.id}
</foreach>
</update>
</mapper>
指定参数位置
如果入参是多个,可以通过指定参数位置进行传参。 如果一个操作需要包含多个参数,例如:查询指定时间范围内的并且指定年龄内的用户 int getUserByTimeAndAge(Date timeBegin, Date timeEnd,); 这份操作的参数有4个,
在 mapper.xml 文件中可以使用 #{arg0},#{arg1},#{arg2},#{arg3} 分别代表第一个到最后一个参数
总结入参和 mapper.xml 中引用的方式
- 单个入参
- 入参为基础数据类型(int/String/char/boolean)
- parameterType为对应的数据类型:int/String
- 引用:#{value} {value},#{}和{}中可以随便写,最后引用的参数都是那个唯一的入参
- 入参是实体类或者实体类的集合
- parameterType为对应的实体类的全限定类名或者别名
- 引用:#{userName} 代表实体类中的 userName 属性
- 入参是一个map(推荐)
- 使用 map 对所有的参数进行封装:map{ name: "Lee", age: 15, begin: "11" end: "22" }
- parameterType不用写
- 引用:#{name} 表示 map 中的 name 值
- 多个入参
- 使用 @Param 的注解
- parameterType不用写
- UserMapper.java 接口类中:int getById(@Param("userId) int id);
- 引用:#{userId}
- 使用 arg 的方式引用
- parameterType不用写
- UserMapper.java 接口类中:int getById(int id, String name, int age);
- 引用:#{arg0},#{arg1},...
总结返回值的情况
- 返回一个实体类或者实体类的集合
- 查询单条数据或者批量查询
- 返回值是一个基础数据类型
- resultType为对应的数据类型 int/String...
- 例如:增删改之后返回一个 int 数代表修改的数据的数量,
- 返回值是一个 map或者 map 的集合
- resultType:map
- 用于没有实体类可以封装对应的数据时,故使用 map 来返回
实体类的成员变量和列名不一致怎么解决
User 实体类中的对用户主键的定义是 String id; t_user 表中对用户主键的列名定义 是 uid; 当这两个不一致的时候怎么处理
- 在 Mapper.xml 文件中使用别名,对在实体类中不一样的列名起对应的别名
<mapper>
<select>
select uid id form t_user where id = 3;
</select>
</mapper>
- 使用 resultMap 映射处理
- attribute
- id:resultMap 的 id,在 sql 标签中使用
- type:映射到的实体类
- resultMap 下的标签
- id:主键绑定
- result:非主键绑定
- property:实体类中的成员变量名称
- column:sql 语句中查询结果中的列名
- collection:集合绑定
- property:实体类中的成员变量名称
- ofType:集合中泛型对应的实体类(全限定类名/别名)
- column:sql 标签中引用作为参数,传递给下一条 sql 语句
- select:指定一条定义好的查询语句,使用 column 提供的参数做做查询,将查询的结果填充到 property 指定的成员变量中
- association:绑定实体类中成员类的变量
- property:实体类中成员类的名称
- javaType:对应实体类的全限定类名或者别名
<mapper>
<!-- 使用 resultMap 手动定义映射内容 -->
<!--
id:映射关系得 id
type:映射到哪个实体类
主键绑定:id 标签
非主键绑定:result 标签
property:实体类中的成员变量
column:数据表中的列名
-->
<resultMap id="dusermap" type="duser">
<id property="uid" column="id" />
<result property="userName" column="userName" />
<result property="ps" column="password" />
<result property="createTime" column="createTime" />
<result property="updateTime" column="updateTime" />
</resultMap>
<select id="getAll" resultMap="dusermap">
select id, userName,password, createTime, updateTime
from t_user;
</select>
</mapper>
表之间的关联关系
- 一对多关联 一个老师教学一个班级的学生
- 多对一关联 一个班级的学生由一个老师来教学
- 一对一关联 一个家教老师只辅导一个学生,一个学生只请了一位家教老师
- 多对多关联 园区的车位和园区内的每一辆车,每一个车位可以停任意一辆车,任意一辆车可以停在任意一个车位上。
一对多的关联关系
一. 一个用户对应有多个文件夹。查询一个用户时,需要把用户的文件夹也查询返回。 sql语句的实现: select u.id, u.userName, f.id fid, f.fname, f.descr, f.createTime, f.updateTime from t_user u inner join t_folder f on u.id=f.userId where u.id = 1;
- 封装 t_folder 对应的实体类 Folder
- 重新构建用户实体类 User,加上一个 Folder 集合的属性表示用户的文件夹集合。
- 在 UserMapper.java 接口文件中定义方法。
- 在 UserMapper.xml 文件中先定义映射关系,再定义 语句 二. 重点是使用 resultMap 来定义实体类和 sql 意图据查询出来的结果的映射关系。 三. 总结来说,无论是什么关联关系,如果某方持有另一方的集合,则使用 标签来完成映射,如果某方持有另一方的对象,则使用 来完成映射。 事务 在 MyBatis 中设置事务的管理方式,在 SqlMapperConfig.xml 中使用 来设置程序员自己控制事务的提交和回滚。 也可以设置自动提交。在获取 sqlSession 对象时 sqlSession = factory.openSession():不传参数或者传 false 为手工提交事务,在增删改之后需要执行 sqlSession.commit() sqlSession = factory.openSession(true):为自动提交,在增删改之后不需要执行 sqlSession。commit() 缓存 什么是缓存 MyBatis提供两级缓存,一级缓存和耳机缓存,默认开启一级缓存。 缓存就是为了提高查询的效率,缓存内存的访问速度会大大快于访问数据库的速度 使用缓存之后的数据访问流程 未开启缓存 客户端 -> 服务端 -> 数据库 数据库查找完成之后返回服务端,再返回客户端 开起缓存机制 客户端 -> 服务端 -> 缓存 -> 数据库 服务端会先在缓存区内查找有没有目标数据有没有在缓存区内。 有直接获取返回 没有再去数据库中查找,数据库查找完毕之后会放一份在缓存区中,下次再查找时就可以从缓冲区中直接获取。 如果修改了数据库中的数据(commit),为了防止缓存中数据跟数据库中的不一致,会直接清空缓存区的全部内容 一级缓存使用的 sqlSession 的作用域,同一个 sqlSession 共享一级缓存的shuju 二级缓存使用的时 mapper 的作用域,不同的 sqlSession 只要访问的时同一个 mapper.xml 文件,则共享二级缓存的作用域。 什么是 ORM ORM(Object Relation Mapping) 对象关系映射 Mybatis 是非常优秀的 ORM 框架 将所有的操作都映射成一个对象来操作,将数据表中的数据映射成某个实体类。通过实体类来操作数据的获取存储使用等过程。 持久化操作:将对象保存到关系型数据库中,将关系型数据库中的数据读取取出来以对象的形式封装 MyBatis 就是一个非常优秀的持久化操作框架。 总结 三层架构 SSM框架(Spring/SpringMVC/MyBatis) 访问控制 动态代理 SqlMapperConfig 优化 批量注册别名 设置日志输出 批量注册 Mapper 文件 #{}和${}占位符的区别 #{} 是占位符 ${} 本质是字符串的替换 返回主键信息 动态 sql