MyBatis
MyBatis 是一款优秀的持久层框架
它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录。
官网: https://mybatis.org/mybatis-3/zh/index.html
优点:
- 易于上手和掌握,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
- sql写在xml里,便于统一管理和优化, 解除sql与程序代码的耦合。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
maven 仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
1. 持久化
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 数据在内存中,断电即失
- 有些数据不能丢失,不能存储在内存中
- 内存贵
- 需要将数据持久化到硬盘中
2. 持久层
Dao层,Service层,Controller层
- 完成持久化工作的代码块
- 层界限十分明显
3. 为什么需要MyBatis
- 帮助程序员将数据存入到数据库中
- 方便
- 传统的JDBC代码太复杂了
- 简化、框架、自动化
优点
- 易于上手和掌握,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
- sql写在xml里,便于统一管理和优化, 解除sql与程序代码的耦合。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
缺点
- 关联表多时,字段多的时候,sql工作量很大。
- sql依赖于数据库,导致数据库移植性差。
- 由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。
- 对象关系映射标签和字段映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql。
- DAO层过于简单,对象组装的工作量较大。
- 不支持级联更新、级联删除。
- Mybatis的日志除了基本记录功能外,其它功能薄弱很多。
- 编写动态sql时,不方便调试,尤其逻辑复杂时。
- 提供的写动态sql的xml标签功能简单,编写动态sql仍然受限,且可读性低。
4. 第一个MyBatis程序
会出现的问题
没有注册MyBatis配置
- 在mybatis-config.xml文件中绑定
<configuration>
<mappers>
<mapper resource="life/leong/dao/UserMapper.xml"/>
</mappers>
</configuration>
资源被过滤
-
在maven中,java包下的xml 文件会被过滤
-
一:在java同级的resource文件下,创建和dao同级的包,在该包下编写xml配置
-
二:在maven中添加资源过滤配置
- maven由于他的约定大于配置,可能会出现写的配置文件无法生效
<!--在build中配置resource,来防止资源导出失败的问题--> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
环境搭建
-
创建一个maven项目
-
导入依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</version> </dependency> </dependencies>
创建一个模块
-
创建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核心配置文件--> <configuration> <!--此处的default为切换数据源,对应environment中的id--> <environments default="development"> <!--可以创建多个--> <environment id="development"> <!--事务管理--> <transactionManager type="JDBC"/> <!--数据源--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <!--数据库类型,连接ip和接口,数据库,编码集,&转义&,SSL安全连接,时区--> <property name="url" value="jdbc:mysql://8.129.123.6:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册--> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
-
编写MyBatis工具类
public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { // 使用MyBatis第一步:获取SqlSessionFactory,格式为官网固定 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } /** * 有了SqlSessionFactory,使用其获得SqlSession实例 * SqlSession完全包含了面向数据库执行sql命令所需要的所有方法 * @return */ public static SqlSession getSqlSession() { // 参数为true,则表示自动提交事务 return sqlSessionFactory.openSession(); } }
-
实体类
public class User { private int id; private String name; private String pwd; }
-
UserDao接口类
public interface UserDao { List<User> getUserList(); }
-
UserMapper.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->绑定一个对应的Dao/Mapper接口--> <!--id对应接口中的方法名字--> <!--resultType:返回结果--> <!--resultMap:返回集合--> <!--mapper中只能写sql中的注释--> <mapper namespace="life.leong.dao.UserDao"> <select id="getUserList" resultType="life.leong.entity.User"> select * from mybatis.user; </select> </mapper>
-
测试类
public class MyTest { @Test public void test() { // 获得SqlSession对象 SqlSession sqlSession = MyBatisUtil.getSqlSession(); // 执行sql // 方式一:getMapper UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); // 方式二:原来的方式,要根据方法的返回值来使用 // List<User> userList = sqlSession.selectList("life.leong.dao.UserDao.getUserList"); for (User user : userList) { System.out.println(user); } // 关闭SqlSession sqlSession.close(); } }
5. 增删改查
mapper映射文件
namespace
命名空间,用来绑定一个对应的Dao/Mapper接口
<mapper namespace="life.leong.dao.UserMapper">
...
</mapper>
select
查询
<select id="getUserById" parameterType="int" resultType="life.leong.entity.User">
select * from mybatis.user where id=#{id}
</select>
- id 对应的为接口中的方法名
- resultType 返回类型
- parameterType 参数类型
- #{} 取值,值对应的名称为接口中的参数名
insert
插入
<insert id="addUser" parameterType="life.leong.entity.User">
insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>
-
获得参数时,实体类可以直接使用变量名
-
测试类
@Test public void test03() { // 增删改需要提交事务 SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.addUser(new User(2, "2", "2")); System.out.println(i); // 提交事务,增删改不提交事务,数据更改不成功 sqlSession.commit(); sqlSession.close(); }
-
增删改,需要提交事务才能更改数据
update
修改
<update id="updateUser" parameterType="life.leong.entity.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
delete
删除
<delete id="deleteUserById" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
6. 配置解析
核心配置文件
mybatis-config.xml
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<configuration>
<environments default="">
<environment id="">
<!--事务管理-->
<transactionManager type=""/>
<!--数据源-->
<dataSource type="">
<property name="" value=""/>
<property name="" value=""/>
<property name="" value=""/>
<property name="" value=""/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
<mappers>
<!--resource-->
<mapper resource=""/>
</mappers>
</configuration>
- 默认的事务管理器:JDBC
- 连接池:POOLED
使用配置文件存储数据源
-
创建db.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai username=root password=root
-
引入外部配置文件
<!--configuration核心配置文件--> <configuration> <!--引入外部配置文件--> <properties resource="db.properties"/> </configuration>
- 可以在引入外部配置文件下添加标签
-
标签的位置有明确的顺序,顺序错误将出错
类型别名配置
typeAliases
起别名,减少包路径冗余
<!--给实体类起别名-->
<typeAliases>
<!--指定一个类-->
<typeAlias type="life.leong.entity.User" alias="User"/>
<!--指定一个包,包下类的别名为首字母小写的类名,大小写均可,小写用于区分-->
<!--如果也要自定义别名,则需要在类上添加@Alias("")注解-->
<package name="life.leong.entity"/>
</typeAliases>
- 指定一个类,自定义别名
- 指定扫描一个包,别名为该包下首字母小写的类名(大写也可,为区分,建议首字母小写)
- 需要自定义别名,在类上添加@Alias("")注解即可
java类型中相应的别名
常见的java类型,mybatis内建了相应的别名
- 基本数据类型前面添加"_"
- 引用数据类型首字母均为小写,Integer的别名为int,int的别名为_int
设置
settings
核心配置文件中的设置,会改变MyBatis运行时的行为
参考官网:https://mybatis.org/mybatis-3/zh/configuration.html
- cacheEnabled
- lazyLoadingEnabled
- logImpl
映射器
mappers
绑定映射文件,告诉MyBatis在哪查找映射语句
-
相对类路径的资源引用:resource,推荐使用
<mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
-
完全限定资源定位符:弃用
<mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers>
-
映射器接口实现类的完全限定类名:class
- 当接口与mapper映射文件不在同一个包下时,无法查找到
- 当接口与mapper映射文件名称不同时,无法查找到
- 要求:接口与映射文件必须同名,必须在同一个包下
<mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
-
包内的映射器接口实现全部注册为映射器:name
- 当接口与mapper映射文件不在同一个包下时,无法查找到
- 当接口与mapper映射文件名称不同时,无法查找到
- 要求:接口与映射文件必须同名,必须在同一个包下。与class一样
<mappers> <package name="org.mybatis.builder"/> </mappers>
7. 生命周期和作用域
生命周期和作用域,是至关重要的,错误的使用会导致严重的并发问题
- 流程
SqlSessionFactoryBuild
- 一旦创建SqlSessionFactory就不需要它了
SqlSessionFactory
- 一旦被创建,运行期间一直存在,类比数据库连接池
- 没有任何理由丢弃它或重新创建另一个实例
- 多次重建占用资源,是不好的
- SqlSessionFactory 的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式。
SqlSession
- 每个线程都应该有它自己的 SqlSession 实例
- 类比连接到连接池的一个请求
- SqlSession 的实例不是线程安全的,因此是不能被共享的
- SqlSession的最佳的作用域是请求或方法作用域
- 不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行,也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中。比如 Servlet 框架中的 HttpSession
- 每次请求完之后都要确保SqlSession关闭,否则资源被占用
- 每一个mapper就代表一个具体的业务
8. 属性名与字段名不一致
实体类中的属性名与数据库中的字段名不一致,在之前的方法中,查询返回的结果为空
- 实体类中的pwd更改为password
- 在执行后,查询出来的结果中,password为null
解决方法
-
方式一:起别名
<select id="getUserList" resultType="life.leong.entity.User"> select id,name,pwd as password from mybatis.user; </select>
-
方式二:resultMap,结果集映射,处理简单的结果集
参考官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
<!--结果集映射--> <resultMap id="userMap" type="life.leong.entity.User"> <!--column数据库中的字段,property实体类中的属性--> <!--什么属性不一致,就只要映射这个属性即可--> <!--<result column="id" property="id"/>--> <!--<result column="name" property="name"/>--> <result column="pwd" property="password"/> </resultMap> <select id="getUserList" resultMap="userMap"> select * from mybatis.user; </select>
-
如果只是基本数据类型和String,则可以在接口中添加@param注解,绑定数据
-
如果数据库是’_'连接,而在实体类中是驼峰式命名,则可以在核心配置文件中的设置里添加属性,实现自动转换
<settings> <setting name="mapUnderscoreToCamelCase" value="STDOUT_LOGGING"/> </settings><sett
9. 日志
打印数据库操作
日志工厂
-
logImpl:指定MyBatis所用日志的具体实现,未指定时将自动查找
- SLF4J
- LOG4J (掌握)
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING (掌握)
- NO_LOGGING
-
具体使用哪个日志实现,在设置中
- STDOUT_LOGGING 标准日志输出
具体实现
STDOUT_LOGGING
标准的日志输出,可以直接使用
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
LOG4J
Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
配置文件参考博客:https://blog.youkuaiyun.com/eagleuniversityeye/article/details/80582140
- 需要导入依赖
- 可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
- 依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 新建配置文件: log4j.properties
### 将等级为DEBUG的日志信息输出到console和file这两个目的地 ###
# 等级可以自定义,使用INFO、DEBUG、ERROR都可以
# 在开发环境下日志级别要设置成 DEBUG ,生产环境设为 INFO 或 ERROR
log4j.rootLogger = DEBUG,console,file
### 输出信息到控制抬 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n
### 输出信息到文件 ###
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./logs/leong.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n
### 日志输出级别 ###
log4j.logger.org.mybatis = DEBUG
log4j.logger.java.sql = DEBUG
log4j.logger.java.sql.Statement = DEBUG
log4j.logger.java.sql.ResultSet = DEBUG
log4j.logger.java.sql.PreparedStatement = DEBUG
- 设置日志实现
<settings>
<!--标准的日志工厂实现-->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="LOG4J"/>
</settings>
- 当核心配置中使用扫描包时,log4j会产生乱码,idea无法打开log文件
- 使用
import org.apache.log4j.Logger;
public class MyTest {
// 使用的类
static Logger logger = Logger.getLogger(MyTest.class);
@Test
public void test() {
// 获得SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// 执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
logger.info(user); // [INFO]标签输出到日志和控制台中
// logger.debug("debug");
// logger.error("error");
}
// 关闭SqlSession
sqlSession.close();
}
}
- 日志级别:INFO、DEBUG、ERROR
10. 分页
mybatis使用分页
回顾mysql中的分页
分页用于缓解数据库夜里,给用户更好的体验
-
语句
select * from user limit 0,3 // 从第一个开始,展示三个 select * from user limit 4 // 只有一个参数,从第一个展示个数 limit 起始位置,页面大小 -- 以下为limit分页 -- limit (n-1)*page_size,page_size -- page_size 为 页面大小 -- n 为 当前页 -- (n-1)*page_size 为 起始值 -- 总页数 为 数据总行数/页面大小
实现
-
接口类
// 分页实现查询 List<User> getUserByLimit(Map<String, Integer> map);
-
mapper映射文件
<select id="getUserByLimit" parameterType="map" resultType="life.leong.entity.User"> select * from mybatis.user limit #{startIndex},#{pageSize} </select>
-
测试类
@Test public void getUserByLimitTest() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map map = new HashMap<String, Integer>(); map.put("startIndex",0); map.put("pageSize", 2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { logger.info(userList); } sqlSession.close(); }
-
升级分页就使用:
limit (n-1)*page_size,page_size,n为当前页
11. 使用注解开发
在接口上添加注解,写入sql语句即可
-
接口
@Select("select * from user") List<User> getUsers();
- 当接口中有多个参数时,建议添加@Param注解,用来绑定select语句中的变量名,以Param注解为准,参数名更改不用对应也行.
@Select("select * from user where id=#{id} and name=#{name}") List<User> getUserByIdName(@Param("id") int id,@Param("name") String Name);
- 参数是实体类时,在注解语句中直接使用实体类的属性名即可
@Update("update user set name=#{name},pwd=#{password} where id=#{id}") List<User> getUserByIdName(User user);
-
接口绑定
<mappers> <mapper class="life.leong.dao.UserMapper"/> </mappers>
- 实现的本质
- 反射机制实现
- 底层:动态代理
12. MyBatis执行流程
- Resources 获取加载全局配置文件
- 实例化SqlSessionBuilder构造器
- 解析配置文件流 XMLConfigBuilder
- Configuration所有的配置信息
- SqlSessionFactory实例化
- transaction事务管理
- 创建executor执行器
- 创建sqlSession
- 实现CRUD:实现失败,回滚 ----> 6 ,关闭
- 是否执行成功:执行失败,回滚 ----> 6 ,关闭
- 成功:提交事务
- 关闭
13. 复杂查询
多对一:关联
数据库中的多对一,例如多个学生对应一个老师
使用对象:
association
- 当多对一时,只需要一个对象,所以在resultMap中使用association标签
按照查询嵌套处理
查询学生所对应的老师:子查询
-
实体类
@Data public class Student { private int uid; private String name; private Teacher teacher; } @Data public class Teacher { private int tid; private String name; }
-
接口类
public interface StudentMapper { List<Student> getStudent(); List<Teacher> getTeacher(); }
-
映射文件
<mapper namespace="life.leong.dao.StudentMapper"> <resultMap id="studentMap" type="Student"> <result property="uid" column="uid"/> <result property="name" column="name"/> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getStudent" resultMap="studentMap"> select * from mybatis.student </select> <select id="getTeacher" resultType="Teacher"> select * from mybatis.teacher where tid=#{tid} </select> </mapper>
- resultMap中的 association 映射对象 类似于子查询,按照查询嵌套处理
- property:对象名称(实体类中)
- column:数据库中对应字段
- javaType:对象类型
- select:子查询语句
-
测试类
@Test public void getStudentTeacher() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); List<Student> studentList = mapper.getStudent(); for (Student student : studentList) { logger.info(student); } sqlSession.close(); }
按照结果嵌套处理
查询学生所对应的老师:联表查询
-
不同的是映射文件
<mapper namespace="life.leong.dao.StudentMapper"> <select id="getStudent" resultMap="studentMap"> select s.uid sid,s.name sname,t.name tname,t.tid tid from mybatis.student s,mybatis.teacher t where s.tid = t.tid </select> <!--按照结果嵌套处理--> <resultMap id="studentMap" type="Student"> <result property="uid" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher"> <result property="tid" column="tid"/> <result property="name" column="tname"/> </association> </resultMap> </mapper>
- select标签中写完整的查询语句,并起别名(方便绑定)
- resultMap中对结果进行嵌套
- Teacher对象嵌套结果
一对多:集合
数据库中的一对多,例如一个老师教多个学生
使用集合:
collection
- 当一对多时,需要多个对象,组成集合,resultMap中使用collection标签
按照结果嵌套处理
-
实体类
@Data public class Student { private int uid; private String name; private int tid; } @Data public class Teacher { private int tid; private String name; private List<Student> students; }
-
接口类
public interface TeacherMapper { /** * 获得指定老师下的所有学生 */ Teacher getTeacherStudent(@Param("tid") int id); }
-
映射文件
<mapper namespace="life.leong.mapper.TeacherMapper"> <resultMap id="teacherMap" type="Teacher"> <result property="tid" column="tid"/> <result property="name" column="tname"/> <!--复杂的属性需要单独处理:对象:association,集合:collection javaType="" 指定属性的类型 ofType="" 集合中泛型的类型 --> <collection property="students" ofType="Student"> <result property="uid" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> </mapper>
-
测试类
@Test public void getTeacherStudent() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); logger.info(mapper.getTeacherStudent(1)); sqlSession.close(); }
14. 动态SQL
根据不同的条件,生成不同的sql语句
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
环境搭建
-
数据库表
CREATE TABLE `blog`( `id` int(4) NOT NULL COMMENT '博客id', `title` VARCHAR(100) NOT NULL COMMENT '博客标题', `author` VARCHAR(30) NOT NULL COMMENT '博客作者', `create_time` DATETIME NOT NULL COMMENT '创建时间', `views` INT(30) NOT NULL COMMENT '浏览量' )ENGINE=INNODB DEFAULT CHARSET=utf8;
-
实体类
@Data @AllArgsConstructor @NoArgsConstructor public class Blog { private int id; private String title; private String author; private Date createTime; private int views; }
-
接口类
public interface BlogMapper { int addBlog(Blog blog); }
-
映射文件
<?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"> <mapper namespace="life.leong.mapper.BlogMapper"> <insert id="addBlog" parameterType="Blog"> insert into mybatis.blog (id, title, author, create_time, views) value (#{id},#{title},#{author},#{createTime},#{views}) </insert> </mapper>
-
测试类
@Test public void addBlog() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); mapper.addBlog(new Blog(1, "1", "1", new Date(), 1)); mapper.addBlog(new Blog(2, "2", "2", new Date(), 2)); mapper.addBlog(new Blog(3, "3", "3", new Date(), 3)); mapper.addBlog(new Blog(4, "4", "4", new Date(), 4)); mapper.addBlog(new Blog(5, "5", "5", new Date(), 5)); logger.info("成功插入数据"); // sqlSession.commit(); // 工具类设置了自动提交事务 sqlSession.close(); }
- 在工具类中,设置了自动提交事务
public static SqlSession getSqlSession() { // 参数默认为false:需要手动提交事务,添加参数true:自动提交事务 return sqlSessionFactory.openSession(true); }
-
核心配置文件改动
<settings> <!--自动转换驼峰命名和下划线分割,将属性名与字段名映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
if
查询博客
使用 if 语句,实现sql的复用,传入不同的参数名或个数,实现不同的查询
-
映射文件
<select id="queryIfBlog" parameterType="map" resultType="Blog"> select * from mybatis.blog where 1 <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </select>
- 了让程序能在不同情况下正常跑,添加了where 1
- 正常应该添加where标签,where下的if标签,会将第一个if的AND或者OR去掉,保证sql能正常执行
-
测试类
@Test public void queryIfBlogTest() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Map map = new HashMap(); // map.put("title", "1"); // map.put("author", "2"); List<Blog> blogs = mapper.queryIfBlog(map); logger.info("博客:" + blogs); sqlSession.close(); }
choose (when, otherwise)
相当于java中的switch-catch语句,或者if-else语句
-
映射文件
<select id="queryChooseBlog" resultType="life.leong.entity.Blog"> select * from mybatis.blog <where> <choose> <when test="title != null"> title = #{title} </when> <when test="author != null"> and author = #{author} </when> <otherwise> and views = 1 </otherwise> </choose> </where> </select>
- 类似switch-catch或者if-else语句,选择其中一个
trim (where, set)
where
<select id="queryIfBlog" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
- where下的if标签,会将第一个if的AND或者OR去掉,保证sql能正常执行
set
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
-
映射文件
<update id="updateBlog" parameterType="map"> update mybatis.blog -- set title = #{title} <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author} </if> </set> where id = #{id} </update>
trim
自定义语句,前缀去除和后缀去除
- 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为
<trim prefix="WHERE" prefixOverrides="AND |OR ">
... # 使用where,开头为and 或者 or将去除
</trim>
- set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<trim prefix="SET" suffixOverrides=",">
... # 使用set,结尾为 , 将去除
</trim>
- trim
<trim prefix="" prefixOverrides="" suffixOverrides="">
...
</trim>
SQL 片段
将sql语句中的部分语句抽取出来,方便复用
-
使用sql标签抽取公共部分
<sql if="if-test"> <if test="title != null"> title = #{title} </if> </sql>
-
在需要的地方导入
<where> <include refid="if-test"/> </where>
-
最好基于单表来定义SQL片段:即不要在片段中执行负责的语句
-
不要存在where标签
-
尽量只要if判断
foreach
对集合进行遍历,在需要多个相同的字段进行查询时
例如根据id查询
-
sql语句
select * from blog where (id=1 or id=2 or id=3) -- 或者 select * from blog where id in(1,2,3)
-
接口方法
List<Blog> queryForeachBlog(Map map);
-
映射文件
<select id="queryForeachBlog" parameterType="map" resultType="Blog"> select * from mybatis.blog <where> <foreach collection="ids" item="id" open="and (" separator="or" close=")"> id=#{id} </foreach> </where> </select>
- collection:
集合
,名字和map中集合相对应的key相同 - item:遍历的
对象名
- open:每条语句的
前缀
- separator:每段语句的
间隔符
- close:每条语句的
后缀
- collection:
-
测试类
@Test public void queryForeachBlogTest() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); List ids = new ArrayList(); // 集合 Map map = new HashMap(); // Map ids.add(1); ids.add(2); map.put("ids", ids); List<Blog> blogs = mapper.queryForeachBlog(map); logger.info(blogs); sqlSession.close(); }
15. MyBatis缓存
放在内存中的临时数据即为缓存
减小和数据库交互的次数,减少系统开销
经常查询,并且不经常改变的数据,使用缓存,。反之不使用
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启:SqlSession级别的缓存,也称为本地缓存。
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义缓存接口Cache,通过实现Cache接口来定义二级缓存
一级缓存
SqlSession级别的缓存
- 默认开启的一级缓存,只在一次SqlSession中有效
- 当创建SqlSession时,即缓存生效,SqlSession.close()之后缓存失效。
- 在缓存有效期间,即SqlSession创建后未关闭,查询两次同一个数据,第二次查询时没有进入数据库中查询,而是在缓存中读取
- 增删改操作,可能会改变原来的数据,所以会必定会刷新缓存,再次查询还是要重新进入数据库查询
- 手动刷新缓存sqlSession.clearCache();
二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
参考官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
- 基于namespace级别的缓存,即一个名称空间(mapper),对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭,这个会话对应的一级缓存就失效了。此时一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存中。
步骤
-
开启全局缓存
<settings> <!--默认是true,但是为了阅读性,显示的开启全局缓存--> <setting name="cacheEnabled" value="true"/> </settings>
-
在namespace中使用
<mapper namespace=""> <!--<cache/>--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> </mapper>
- 可以修改相应参数,也可用默认的
- 测试
- 测试的时候,在测试类中创建两个SqlSession
- 用一个SqlSession查询之后关闭这个SqlSession
- 再用另一个SqlSession查询同样的数据,此数据是从二级缓存中得到的
- 在没有使用策略时,即使用<cache/>,没有readOnly=“true”,需要给实体类序列化:实体列实现
Serializable
接口 创建实体类时,都序列化实体类
缓存原理
- 用户先在mapper中的二级缓存进行查询
- 没有查询到,再到SqlSession中的一级缓存查询
- 没有查询到,最后再到数据库中查询
- 查询到的数据保存在一级缓存中
- SqlSession失效,一级缓存的数据保存到二级缓存中