Mybatis总结
概述
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如加载驱动、建立连接、创建sql语句执行对象如statement、手动设置参数、获取结果集等jdbc繁杂的过程代码。简言之。就是简化数据库增删改查代码。
使用Java操作数据库的话,JDK给我们提供了一层对各个数据库的封装,也就是JDBC,它屏蔽了数据库之间的差异,使用JDBC可以统一操作。
mybatis安装配置
在工程目录下的pom.xml中配置
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.0</version>
</dependency>
Mybatis核心配置文件(mybatis-config.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>
<!-- 引入外部properties文件 -->
<properties resource="jdbc.properties" />
<!-- 定义类型别名,在xxxMapper.xml文件中就可以用别名代替很长的类名 -->
<!--<typeAliases>
<typeAlias type="com.lanou.spring.bean.User" alias="User" />
</typeAliases>-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 配置不同环境的参数 -->
<!-- 定义数据库信息,默认使用id是development数据库构建环境 -->
<environments default="development">
<!-- 开发环境数据库、事务配置 -->
<environment id="development">
<!-- 事务管理使用JDBC的事务 -->
<transactionManager type="JDBC"/>
<!-- 配置开发环境数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 将mapper SQL映射文件包含进来 -->
<mappers>
<mapper resource="mappers/userMapper.xml"/>
</mappers>
</configuration>
sqlSessionFactory
一个该对象对应一个数据源。
SqlSessionFactory是一个工厂接口,而不是一个实现类。它的任务就是创建SqlSession。SqlSession类似于一个JDBC的Connection对象。
从XML中构建,如上的核心配置文件
SqlSession
一个sqlsession 对象代表一次到数据库的会话
功能:
用于负责执行sql语句的对象
操作事务:提交,回滚
非线程安全,不能作为一个类的静态变量,最好是用完后
在finally中关掉它
mapper
用于映射数据库CRUD操作,将SQL语句和Java接口进行绑定
基于xml方式映射SQL
基于注解方式映射SQL
通过SqlSession对象调用相应的mapper接口完成对数据库的操作
核心配置文件中的settings
- mapUnderscoreToCamelCase
是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名
A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
如private Timestamp lastlOgintime;
字段last_login_time ,下划线参与,但字母不分大小写
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
-
useGeneratedKeys
允许 JDBC 支持自动生成主键,需要驱动支持。
如果设置为 true 则这个设置强制使用自动生成主键<!-- 给insert语句添加useGeneratedKeys、keyProperty后,mybatis会将自增的id值直接赋值到传进来的user对象的id属性上 useGeneratedKeys: 指定需要获取数据库自增的id keyProperty: 指定自增字段的名称 --> <!--<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into user(nick_name,account,password,last_login_time) 字段名 values (#{nickName},#{account},#{password},#{lastLoginTime})--> #{属性名}
结果集映射
ResultType方式
ResultType方式适用于数据库结果集可以直接映射成一个Java类的情况
实体类
@Getter
@Setter
@ToString
public class User {
private Integer id;
private String nickName;
private String account;
private String password;
private Timestamp lastLoginTime;
//private Timestamp lastlOgintime;
}
使用方法
<select id="selectAllUser" resultType="user">
select * from user
</select>
ResultMap方式
ResultMap方式适用于复杂的结果集映射,比如数据库返回的结果集中的列名和JavaBean无法一一对应,或者对象间存在一对一、一对多关联映射时。
解决数据库列名与Java类中属性名不一致的映射问题
上述的settings无法满足要求
如private Timestamp lastloginttime;
<resultMap id="userMap" type="user">
<id property="id" column="id" />
<result property="nickName" column="nick_name"/>
<result property="account" column="account"/>
<result property="password" column="password"/>
<result property="lastloginttime" column="last_login_time"/>
</resultMap>
<!--<select id="selectAllUser" resultType="user">
select * from user
</select>-->
<select id="selectAllUser" resultMap="userMap">
select * from user
</select>
解决一对一及一对多映射查询问题
示例:
xxxMapper.xml模板
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 对应接口的全名 -- >
<mapper namespace="com.lanou.spring.mapper.xxxMapper">
</mapper>
mybatis-config.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>
<!-- 引入外部properties文件,路径从类路径的根目录开始 -->
<properties resource="jdbc.properties" />
<typeAliases>
<!--单个类配置别名-->
<!-- <typeAlias type="com.lanou.spring.bean.King" alias="King" />-->
<!--统一配置某个包下的所有类的别名-->
<package name="com.lanou.spring.bean"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="kingMapper.xml"/>
</mappers>
</configuration>
解决一对一映射查询问题
实体类:
@Getter
@Setter
@ToString
public class King {
private Integer id;
private String name;
private Queen queen;
}
@Setter
@Getter
@ToString
public class Queen {
private Integer id;
private String name;
private Integer kId;
}
包 com.lanou.spring.mapper 里放接口,内部有数据库的增删改查方法
public interface KingMapper {
King findKingByKingId(Integer id);
}
jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/lanou?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=123456
kingMapper.xml
<mapper namespace="com.lanou.spring.mapper.KingMapper">
<!--映射结果集 -->
<resultMap id="kingMap" type="king" >
<id property="id" column="kid"></id>
<result property="name" column="kname" />
<association property="queen" javaType="queen" resultMap="queenMap"/>
</resultMap>
<resultMap id="queenMap" type="queen">
<id property="id" column="qid"/>
<result property="name" column="qname"/>
<result column="qkid" property="kId" />
</resultMap>
<!--id 对应接口中的方法名,引用定义的resultmap,将查询的结果集映射到kingMap上 -->
<select id="findKingByKingId" resultMap="kingMap">
select k.id kid, k.name kname,q.id qid,q.name qname,q.k_id qkid
from king k
LEFT JOIN queen q
on q.k_id = k.id
where k.id = #{id}
;
</select>
</mapper>
解决一对一映射查询问题
实体类
@Getter
@Setter
@ToString
public class King1 {
private Integer id;
private String name;
private List<Girl> girls;
}
@Setter
@Getter
@ToString
public class Girl {
private Integer id;
private String name;
private Integer kId;
}
接口
public interface KingMapper {
King1 findKingByKingId(Integer id);
}
kingMapper.xml
<resultMap id="kingMap" type="king1" >
<id property="id" column="kid"></id>
<result property="name" column="kname" />
<collection property="girls" ofType="girl" resultMap="girlMap"/>
</resultMap>
<resultMap id="girlMap" type="girl">
<id property="id" column="gid"/>
<id property="name" column="gname"/>
<id property="kId" column="gkid" />
</resultMap>
<select id="findKingByKingId" resultMap="kingMap">
select k.id kid, k.name kname,g.id gid,g.name gname,g.k_id gkid
from king k
LEFT JOIN girl g
on g.k_id = k.id
where k.id = #{id}
;
</select>
测试类
public class TestMain {
public static void main(String[] args) throws IOException {
InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource
);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
KingMapper mapper = sqlSession.getMapper(KingMapper.class);
/* King king = mapper.findKingByKingId(32);
System.out.println(king);*/
King1 king1 = mapper.findKingByKingId(31);
System.out.println(king1);
}
}
批量插入
通过forEach动态SQL方式
原理是直接通过foreach动态标签,根据传过来的参数数量动态生成一个很长的sql语句,一个语句就是一次批量插入
接口
public interface UserMapper1 {
int batchInsert(List<User1> user1List);
}
UserMapper.xml
<insert id="batchInsert">
insert into user(account,status,createtime)
values
<foreach collection="list" item="user" close=";" separator=",">
(#{user.account},#{user.status},#{user.createTime})
</foreach>
</insert>
测试类
public static void main(String[] args) throws IOException {
InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource
);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);
testBatchInsertexecutor(sqlSession, mapper);
}
public static void testBatchInsert(UserMapper1 mapper){
List<User1> user1List = new ArrayList<>();
user1List.add(new User1("谁1",1,new Timestamp(System.currentTimeMillis())));
user1List.add(new User1("谁2",1,new Timestamp(System.currentTimeMillis())));
user1List.add(new User1("谁3",1,new Timestamp(System.currentTimeMillis())));
int rows = mapper.batchInsert(user1List);
System.out.println(rows);
}
通过Executor.BATCH的方式
实现原理:在底层的mapper接口和mapper映射文件中,都只是一个普通插入单条数据的写法,它通过在上层获取sqlSession时,指定执行类型是批量ExecutorType.BATCH方式,实现每次执行完单条数据以后并没有真正写入数据库,只有当调用sqlSession.flushStatements(),才会将数据一次写入数据库。
接口
public interface UserMapper1 {
int batchInsertExecutor(User1 user);
}
UserMapper.xml
<insert id="batchInsertExecutor">
insert into user(account,status,createtime)
values
(#{account},#{status},#{createTime})
</insert>
测试类
public static void testBatchInsertexecutor(SqlSession sqlSession, UserMapper1 mapper){
List<User1> user1List = new ArrayList<>();
for(int i = 1; i < 30; i++){
user1List.add(new User1("芒种1",1,new Timestamp(System.currentTimeMillis())));
user1List.add(new User1("芒种2",1,new Timestamp(System.currentTimeMillis())));
user1List.add(new User1("芒种3",1,new Timestamp(System.currentTimeMillis())));
}
int batchSize = 8; //设置批量添加条数,等到flushStatements()执行完,即写到数据库里
int count = 0 ;
List<BatchResult> resultList = new ArrayList<>);
for(User1 user: user1List){
mapper.batchInsertExecutor(user);
count++;
if(count % batchSize == 0){
resultList.addAll(sqlSession.flushStatements());
}
}
if(count % batchSize != 0 ){
resultList.addAll(sqlSession.flushStatements());
}
int rows = 0;
for(BatchResult batchResult: resultList){
int[] updateCounts = batchResult.getUpdateCounts(); //某一批影响的条数
rows += updateCount;
}
}
System.out.println("批量插入成功,响应的行数:" + rows);
}
缓存
一级缓存(本地缓存)
一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。
即在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
第二次以后是直接去缓存中取。
当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。
/**
* 一级缓存(本地缓存)默认已经启用了本地的会话缓存
*/
public class TestMain {
public static void main(String[] args) throws IOException {
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
testLocalCache(sqlSession );
}
public static void testLocalCache(SqlSession sqlSession ){
//同一个sqlSession执行同样的sql语句,会命中缓存
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
for(int i = 0; i < 5; i++){
List<Stu> stuList = mapper.selectAll();
for(Stu stu: stuList){
System.out.println("第" + i + "次");
System.out.println(stu);
}
}
//更新操作会使一级缓存失效,下次自动从数据库查询最新数据
Stu stu = new Stu();
stu.setSid(120);
stu.setSmajor("网工");
mapper.updateStu(stu);
List<Stu> stuList1 = mapper.selectAll();
for(Stu stu1: stuList){
System.out.println(stu1);
}
}
}
禁用一级缓存
-
在映射文件StuMapper.xml中给对应的select标签上添加flushCache=“true”属性
<select id="selectAll" resultMap="BaseResultMap" flushCache="true"> select sid, sname, ssex, sage, smajor, sphone, isDelete from stu </select>
-
在核心配置文件mybatis-config.xml中将localCacheScope设置成STATEMENT(默认值是SESSION)
<settings>
<!-- 开启将数据库中下划线连接的字段自动映射为Java的小驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
二级缓存(全局缓存)
什么是二级缓存?
Mybatis中二级缓存相比一级缓存来说是一个作用域更大的缓存方案。 二级缓存的作用域可以跨多个SqlSession,只要是同一个namespace下的mapper映射文件都可以共享缓存。但是不能跨SqlSessionFactory。
Mybatis二级缓存需要手动开启。
1打开全局二级缓存开关
<setting name="cacheEnabled" value="true"/>
在具体的Mapper中开启二级缓存
<cache/>
<!-- <cache>标签表示当前这个 mapper 映射将使用二级缓存,能否命中二级缓存就看多次查询是否属于同一个namespace。-- >
2、创建多个SqlSession对象去执行同样的查询语句
public static void testSecondCache(SqlSessionFactory sqlSessionFactory) {
for(int i = 0; i < 5; i++){
SqlSession sqlSession = sqlSessionFactory.openSession(true);
StuMapper mapper = sqlSession.getMapper(StuMapper.class);
List<Stu> stuList = mapper.selectAll();
for(Stu stu: stuList){
System.out.println("第" + i + "次");
System.out.println(stu);
}
sqlSession.close();
}
}
3、观察日志,查看真正执行了多少次查询
4、使用任意一个SqlSession执行一次更新操作,然后再用其他SqlSession执行查询,观察查询是否命中缓存,结果是否是更新后的最新结果
更新操作后,命中率降低后,重新查数据库。只要是发生了增删改的操作,触发缓存更新
注意: 由于二级缓存是事务性的,只有提交了事务才会写入缓存中。所以测试的时候不要忘记提交事务(或者直接close SqlSession也行)
二级缓存注意事项
当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化 方式来保存对象。
public class Stu implements Serializable { }