MyBatis
- mybatis官方文档:https://mybatis.org/mybatis-3/zh/
- mybatis-spring官方文档:http://mybatis.org/spring/zh/
- mybatis-spring-boot-autoconfigure
- 教程:https://how2j.cn/k/mybatis/mybatis-tutorial/1087.html
xml方式使用步骤
1.引入依赖:数据库驱动+mybatis
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
</dependencies>
2.建库建表建项目
3.创建实体类
- 需要Getter和Setter
- 实体类不需要指定表名,不需要指定表字段名
4.配置文件mybatis-config.xml
- 位置:maven工程放在resources目录下
- 作用:
- 配置数据库连接信息;
- 注册XxxMapper.xml(注解方式中是注册XxxMapper.class)
- 指定实体类的包路径
- 配置插件
- 配置二级缓存
- 标签:
- properties标签可以引入外部properties文件的内容(例如数据库配置信息),然后通过${key}来引用,这个标签少用
- 也可以不使用 mybatis-config.xml 而使用Java代码配置类
5.SQL映射文件Xxx.xml
-
位置:maven工程放在resources目录下
-
作用:SQL 语句映射
-
标签:
- insert,delete,update,select,resultMap,sql,include
-
输入映射:
parameterType
和parameterMap
,基本不用指定这两个属性- parameterType
- 基本类型
- String
- POJO
- Map
- List
- 数组
- parameterMap,已废弃
- parameterType
-
获取参数
-
输出映射:
resultType
和resultMap
- resultType,一般用于查询返回基本数据类型,或者没有组合其他引用对象的实体类,故一般不用指定
- insert,delete,update可以返回int,Integer,long,Long,boolean,Boolean,void,MyBatis会根据接口方法的返回类型自动处理
- resultMap,常用,用来告诉Mybaits如何将返回的查询结果进行封装,使用场景:引用了其他引用对象的实体类,或者属性和字段不一致的实体类,又或者多表查询时多表存在同名字段时需要为同名字段起别名
- id标签指定主键,属性有column和property
- result标签指定普通列,属性有column和property
- collection将关联查询信息映射到一个pojo类中,属性有property和javaType(指定POJO类型)
- association将关联查询信息映射到一个list集合中,属性有property和ofType(指定集合中元素类型)
- collection和association中的关联查询可转换为分步查询,使用selectKey指定要查询的SQL语句,并用column指定第一步查询的结果中的哪个列作为参数传递给第二步查询;传递多个参数时,使用column={key1=column1,key2=column2}
- 分步查询可以实现懒加载,需要在collection或association标签中指定
fetchType=lazy
,或者mybatis-configa.xml中指定懒加载配置
- resultType,一般用于查询返回基本数据类型,或者没有组合其他引用对象的实体类,故一般不用指定
-
ofType和javaType区别:博客
-
select返回一条记录时,可以返回POJO(指定resultType或resultMap);也可以返回Map<String,Object>,指定resultType=“map”
-
select返回多条记录时,可以返回List(指定resultType或resultMap);也可以返回Map<Integer,Emp> ,这里Map的key是主键,Emp是一条记录,resultType=“map”
-
占位符:#{}解析传递进来的参数数据,${}对传递进来的参数原样拼接在SQL中
-
${} 有安全风险,不要拼接用户提交的内容。用途举例:分表,动态指定表名
-
#和$区别:博客
6.MyBatis API
- 看源码
7.CURD
- 读取配置文件mybatis-config.xml,
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
- 获取全局SqlSessionFactory
- 获取当前线程的SqlSession(和Connection一样非线程安全)
- 默认事务开启,通过SqlSession执行SQL语句
- 提交事务(insert,update,delete需要)
- 关闭SqlSession
8.Mapper动态代理👍
- 接口式编程
- 可以自定义自己的接口,不使用MyBatis原生接口
原生:Dao ====> DaoImpl
mybatis:Mapper ====> xxxMapper.xml
- 配置文件Xxx.xml改名为XxxMapper.xml
- 在mapper包下新建XxxMapper.java接口,接口中的方法名即为XxxMapper.xml中的statement id
- MyBatis会为接口生成代理对象
Mapper接口开发需要遵循以下规范:
1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。
2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同
4、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
- 动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
9.获取自增主键
-
MySQL,insert标签上增加属性
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
-
Oracle,自增主键使用序列,insert标签内在insert语句前使用selectKey,先给实体类的id属性赋值
<selectKey resultType="long" order="BEFORE" keyProperty="deptId"> select seq_sys_dept.nextval as deptId from dual </selectKey>
10.模糊查询
-
MySQL:select * from category where name like concat(‘%’,#{0},‘%’)
-
Oracle:select * from category where name like ‘%’||#{0}||‘%’
注解方式使用步骤
- 在XxxMapper.java接口方法上写SQL语句,
@Insert,@Delete,@Update,@Select
- 高级映射,多表查询,
@Results,@Result
- 动态SQL:SQL类,用Java代码拼接SQL,丑陋!
jdbcType和javaType对应关系
实体关系
一对多
- 一侧实体类组合一个多侧实体类的list
- 一侧映射文件resultMap指定一个collection
- Category中有List<Product>属性
- resultMap中指定collection
多对一
- 多侧实体类组合一个一侧实体类对象
- 多侧映射文件resultMap指定一个association
- Product中有Category属性
- resultMap中指定association
多对多
- 需要一个中间实体类和一张中间表
- 选一个多侧实体类维护一个中间实体类的list
- 上述多侧映射文件resultMap指定一个collection,collection中又有association
- Order中有List<OrderProduct>属性
- Product中有List<OrderProduct>属性
- OrderProduct中有Order,Product属性
- 建立关系,删除关系,查询关系
- 多对多不存在修改关系的做法,就是删除旧的,然后新增一条即达到修改的效果
动态SQL
ONGL表达式
if 标签
<select id="listProduct" resultType="Product">
select * from product
<if test="name!=null">
where name like concat('%',#{name},'%')
</if>
</select>
where 标签
- 会进行自动判断:如果任何条件都不成立,那么就在sql语句里就不会出现where关键字;如果有任何条件成立,会自动去掉多出来的 and 或者 or
<select id="listProduct" resultType="Product">
select * from product
<where>
<if test="name!=null">
and name like concat('%',#{name},'%')
</if>
<if test="price!=null and price!=0">
and price > #{price}
</if>
</where>
</select>
set 标签
- 使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号
<update id="updateProduct" parameterType="Product" >
update product
<set>
<if test="name!=null">name=#{name},</if>
<if test="price!=null">price=#{price}</if>
</set>
where id=#{id}
</update>
trim标签
- 可以代替或增强where,set标签的功能
- 少用
foreach 标签
<select id="listProduct" resultType="Product">
SELECT * FROM product
WHERE ID in
<foreach item="item" index="index" collection="list" open="(" separator="," close=")">
#{item}
</foreach>
</select>
- MySQL批量插入:insert into values
- Oracle批量插入:insert into select from或者begin 多条插入语句 end;
choose when otherwise标签
- 条件分支,choose标签下包含when,otherwise标签,when标签中判断
sql、include标签
- SQL语句复用:sql标签定义要复用的SQL语句,并指定id属性;include标签通过refid指定要引用的已定义的SQL语句
bind标签
- 对参数进行处理后封装成一个新的参数,例如模糊查询在变量前后加上%
日志
在运行日志中显示SQL语句,下面以log4j为例
- 导入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 配置log4j.properties
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
# 对映射文件配置日志记录
log4j.logger.com.lee=TRACE
# 包级别
#log4j.logger.com.lee.mapper=TRACE
# 接口级别
#log4j.logger.com.lee.mapper.CategoryMapper=TRACE
# 方法级别
#log4j.logger.com.lee.mapper.addCategory=TRACE
# 如需对 XML 文件记录日志,只要对命名空间增加日志记录功能即可
#log4j.logger.com.lee.mapper.CategoryMapper=TRACE
#log4j.logger.com.lee.mapper.CategoryMapper.addCategory=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
延迟加载
- 关联查询变为分步查询
- 需要配置文件mybatis-config.xml
分页
mysql
- limit offset,size ,手动传入offset,size
oracle
- ROWNUM
pagehelper插件
- 官网:https://pagehelper.github.io/
- 文档:https://pagehelper.github.io/docs/
- GitHub:https://github.com/pagehelper/Mybatis-PageHelper
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
PageHelper.startPage(1, 5);
批量处理
- 拼接SQL的长度是有限的
- 解决方法:获取sqlSession时指定BATCH处理器,
SqlSession sqlSession =sqlSessionFactory.openSession(ExcutorType.BATCH)
- SpringBoot,参考博客1,博客2
//获取sqlsession
//从spring注入原有的sqlSessionTemplate
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
void batchInsert() {
// 新获取一个模式为BATCH,自动提交为false的session
// 如果自动提交设置为true,将无法控制提交的条数,改为最后统一提交,可能导致内存溢出
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);
//通过新的session获取mapper
fooMapper = session.getMapper(FooMapper.class);
int size = 10000;
try {
for (int i = 0; i < size; i++) {
Foo foo = new Foo();
foo.setName(String.valueOf(System.currentTimeMillis()));
fooMapper.insert(foo);
if (i % 1000 == 0 || i == size - 1) {
//手动每1000个一提交,提交后无法回滚
session.commit();
//清理缓存,防止溢出
session.clearCache();
}
}
} catch (Exception e) {
//没有提交的数据可以回滚
session.rollback();
} finally {
session.close();
}
}
缓存
一级缓存
-
SqlSession级别,默认开启。
-
原理:通过一个Map来实现同一个sqlsession再次发出相同的sql,就从缓存中取不走数据库。
-
与Spring整合之后,使用的是Mappper代理对应,一级缓存是失效的!!!
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构**(HashMap****)**用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率
二级缓存
-
SqlSessionFactory级别,要手动开启,在XxxMapper.xml中添加
<cache/>
标签开启支持,而且使用二级缓存时候必须将实体类序列化。刷新缓存。 -
二级缓存在Mapper动态代理中是mapper级别,不同的Mapper实例缓存不共享。
-
作用域是mapper的同一个namespace
-
实体类要实现序列化接口
-
避免使用二级缓存(可能会读到脏数据)
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率
整合第三方缓存
- ehcache
事务管理
- 少用,一般使用Spring进行事务管理
- mysql表的类型必须是INNODB
逆向工程
- MyBatis Generator(了解即可)