Mybatis笔记
一、初识Mybatis
1.简介
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
Mybatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
持久化:
将数据瞬时状态(内存)和持久状态转换的机制,持久状态可以理解为如硬盘的存储设备。
为什么需要持久化?
内存断电数据即失,而硬盘可以长久的保存数据。
其次,内存成本过高,不能存大量数据。
持久层:
即完成持久化的代码块,我们的Dao层(Date Access Object)即持久层;
Mybatis可以帮助开发人员减去在Dao层一些重复的操作。
2.入门
要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
二、第一个Mybatis程序
1.配置mybatis-cofig.xml
当我们在pom.xml中引入了mybatis的依赖后,建立一个新项目取名mybatis-01。

在src/main/resources中新建一个mybatis-config.xml文件并添加以下代码
<!--configuration核心配置文件--> <configuration> <!-- environments环境配置--> <environments default="development"> <!-- environment环境变量--> <environment id="development"> <!-- transactionManager事务管理器--> <transactionManager type="JDBC"/> <!-- dataSource数据源--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value=你数据库的连接/> <property name="username" value=你的数据库名/> <property name="password" value=你数据库的密码/> </dataSource> </environment> </environments> </configuration>
注意:xml文件中数据库连接里面的&号需要用&来代替
之后,就可以去数据库建立一张表供我们测试,我这里建了一个user表,有三个字段:id、name、pwd
随便填一些数据,再去建立对应的实体类User,有了实体类就可以去写dao层了
在dao文件夹新建一个UserMapper接口
public interface UserMapper { //查询所有用户 List<User> getUserList(); }
接着,在这个文件夹新建一个UserMapper.xml文件
<!--namespace=绑定一个对应的Dao/Mapper接口--> <mapper namespace="com.gsy.dao.UserMapper"> <!-- select 查询语句 id对应方法名
resultType写你自己建的实体类的全限定类名。 --> <select id="getUserList" resultType="com.gsy.pojo.User"> select * from mybatis.user </select> </mapper>
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
SqlSessionFactory由SqlSessionFactoryBuild构建,一旦SqlSessionFactory产生,SqlSessionFactoryBuild也就没用了,所以它最好是存在局部作用域中。
而SqlSessionFactory一旦被创建就应该在运行期间一直存在且不要多次重复创建,所以它的最佳作用域是应用作用域,最简单的就是使用单例模式或者静态单例模式。
我们把它提取成公共类:MybatisUtils.java
public class MybatisUtils { 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) { throw new RuntimeException(e); } } //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。 // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。 // 可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
这时我们写一个测试类UserMapperTest.java
public class UserMapperTest { @Test public void test(){ //第一步获得SqlSession对象 SqlSession sqlSession= MybatisUtils.getSqlSession(); //方式一:getMapper UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } //关闭SqlSession sqlSession.close(); } }
这时运行肯定会报错,因为我们的mybatis-config.xml文件中没有为UserMapper.xml注册,我们在mybatis-config.xml末尾加上:
<!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册--> <!-- mappers映射器--> <mappers> <mapper resource="com/gsy/dao/UserMapper.xml"/> </mappers> </configuration>
这时再去运行就能拿到全部用户了。
三、配置
1.environments(环境)
Mybatis可以配置多种环境,但是每个SqlSessionFactory实例只能选择一种环境,每个数据库对应一个SqlSessionFactory实例。
如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推。
2.properties(属性)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
<properties resource="db.properties"></properties>
db.properties是我们在resources文件夹下新建的配置文件,用来存放连接数据库的信息:dirver、url、username、password
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如:
<dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource>
我们也可以在<properties>标签中添加一些信息:
<properties resource="db.properties"> <property name="username" value=你数据库名/> <property name="password" value=你数据库密码/> </properties>
这时删掉db.properties中的username和password还能成功运行。
这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 config.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。
值得注意的是,当我们已经引入了外部配置文件,mybatis数据库配置的参数会优先去找外部配置文件 而不是上方在<properties>标签中的参数。
3.typeAliases(别名)
1>(java bean较少时适合)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<!-- 类型别名--> <typeAliases> <typeAlias type="com.gsy.pojo.User" alias="user"></typeAlias> </typeAliases>
这样一来,我们在UserMapper.xml中写的resultType可以直接写别名而不用写全限定名了
这个别名可以使用在任何使用 com.gsy.pojo.User 的地方
2>(java bean较多时适合)
也可以通过扫描包的方式来取别名,Mybatis会在包名下搜索需要的Java Bean
<typeAliases> <package name="com.gsy.pojo"/> </typeAliases>
在没有注解的情况下,会自动使用类名的首字母小写的非限定类名来作为别名(当然大写也没关系,为了规范统一首字母小写)
如果有注解,则别名为其注解值。

注意:使用注解的前提是你在核心配置文件中采用了扫描包的方式取别名,否则注解的别名不会生效。
4.mappers(映射器)
可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="com/gsy/dao/UserMapper.xml"/> </mappers>
<!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="com.gsy.dao.UserMapper"/> </mappers>
<!-- 将包内的映射器接口全部注册为映射器 --> <mappers> <package name="com.gsy.dao"/> </mappers>
需要注意的是:使用class或扫描包的方式注册mapper 需要接口和其对应的mapper配置文件在同一包下而且同名。
5.解决属性名和字段名不匹配
当我们把实体类User中的pwd改成password后,再去查询,密码的结果为null,
这是由于MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。
我们可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。
但是当字段较多时这种方法就比较麻烦了,这时可以用resultMap结果集映射帮我们将字段映射到属性上。
用法很简单:
<!--结果映射 将列映射到实体类的属性--> <resultMap id="UserMap" type="user"> <!--column 数据库中的字段 property 实体类的属性--> <result column="pwd" property="password"></result> </resultMap> <!--
select 查询语句 id对应方法名 --> <select id="getUserList" resultMap="UserMap"> select * from mybatis.user </select>
对于字段和属性名匹配的我们可以不用配置(也可以写上),只需配置不匹配的。
四、日志工厂
-
LOG4J
1.配置
1.导入log4j的包,也可以在pom.xml添加依赖:
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
2.在resources文件夹配置log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码 log4j.rootLogger=DEBUG,console,file #控制台输出的相关设置 log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.Target = System.out log4j.appender.console.Threshold=DEBUG 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=./log/gsy.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{yy-MM-dd}][%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
3.在mybatis-config.xml中添加配置:
<settings> <!-- 标准日志工厂的实现--> <!-- <setting name="logImpl" value="STDOUT_LOGGING"/>--> <setting name="logImpl" value="LOG4J"/> </settings>
2.简单使用
1.在需要使用log4j类的中导入包:
import org.apache.log4j.Logger;
2.测试,日志对象为当前类的class
public class UserMapperTest { static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void test(){ SqlSession sqlSession= MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } sqlSession.close(); } @Test public void log4jTest(){ logger.info("info:进入了log4jTest"); logger.error("error:进入了log4jTest"); logger.debug("debug:进入了log4jTest"); } }
3.日志级别
logger.info("info:进入了log4jTest");
logger.error("error:进入了log4jTest");
logger.debug("debug:进入了log4jTest");
五、分页
1.为什么要分页
减少数据的处理量
2.使用Limit分页
sql语句使用分页的语法是
select * from user limit startIndex,pageSize;
startIndex是从第几个开始(数据库中的列表下标从0开始)
pageSize是显示多少条记录,pageSize=2 就是显示两条记录。
1.接口
//分页 List<User> getUserByLimit(Map<String,Integer> map);
2.mapper.xml
<!-- 分页--> <select id="getUserByLimit" parameterType="map" resultMap="UserMap"> select * from mybatis.user limit #{startIndex},#{pageSize} </select>
3.测试
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<>(); map.put("startIndex",1); map.put("pageSize",2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close();
3.使用RowBounds分页
不推荐,作为了解
1.接口
//分页 List<User> getUserByLimit(Map<String,Integer> map);
2.mapper.xml
<!-- 分页2--> <select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user </select>
3.测试
@Test public void getUserByRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds实现 RowBounds rowBounds = new RowBounds(1, 2); //通过java代码层面实现分页 List<User> userList = sqlSession.selectList("com.qjd.dao.UserMapper.getUserByRowBounds",null,rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
4.分页插件
mybtais分页插件PageHelper
六、使用注解开发
1.注解在接口上实现
//查询所有用户 @Select("select * from user") List<User> getUsers();
增删改查使用@Insert @Delete @Update @Select
2.在核心配置文件中绑定接口
<!--绑定接口--> <mappers> <mapper class="com.gsy.dao.UserMapper"/> </mappers>
现在,无需mapper.xml映射文件也能实现查询了,但使用注解进行复杂的sql语句查询时
较为麻烦,所以简单的sql语句可以用注解在接口实现,复杂的仍然在映射文件中实现(记得在核心配置文件中绑定mapper)
3.测试
public class UserMapperTest { @Test public void test(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //底层主要应用反射 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); for (User user : users) { System.out.println(user); } sqlSession.close(); } }
4.Mybatis执行流程

5.@Param(" ")注解
-
- 基本类型的参数或者String需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略,但是建议大家都加上
- 我们在sql中引用的就是我们这里的@Param(“”)中设定的属性名
6.#{ }和${ }
1、#{}是预编译处理,$ {}是字符串替换
2、mybatis在处理两个字符时,处理的方式也是不同的:
(1)处理#{}时,会将sql中的#{}整体替换为占位符(即:?),调用PreparedStatement的set方法来赋值;
(2)在处理 $ { } 时,就是把 ${ } 替换成变量的值。
3、假如用${}来编写SQL会出现:恶意SQL注入,对于数据库的数据安全性就没办法保证了
4、使用 #{} 可以有效的防止SQL注入,提高系统安全性:
预编译的机制。预编译是提前对SQL语句进行预编译,而后再调用SQL,注入的参数就不会再进行SQL编译。
而SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译时SQL时轻而易举的通过,从而导致数据泄露。而预编译机制则可以很好的防止SQL注入。
7.LomBok
(偷懒用)
1.在IDE中下载LomBok插件(2020.3版本之后的IDE自带LomBok无需下载。)
2.在pom.xml中导入LomBok的包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
3.在实体类中加入注解
@Data//无参构造、get和set hashcode、equals、toString @AllArgsConstructor//有参构造器 @NoArgsConstructor//无参构造
七、一对多/多对一
1.环境搭建
①数据库
CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); CREATE TABLE `student` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, `tid` INT(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fktid` (`tid`), CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8 INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
②实体类
@Data @Alias("Student") public class Student { private int id; private String name; //每个学生都要关联老师 private Teacher teacher; }
@Data @Alias("Teacher") public class Teacher { private int id; private String name; }
这里我使用了LomBok和别名注解,记得引入LomBok的包
③接口
StudentMapper.java
public interface StudentMapper {
List<Student> getStudents();
List<Student> getStudents2();
}
TeacherMapper.java
public interface TeacherMapper { @Select("select * from teacher where id = #{tid}") Teacher getTeacher(@Param("tid") int id); }
④XML映射文件
现在把映射文件放在pojo包中就显得有些乱了,我们可以再resources中新建一个同名包:com.gsy.pojo
在此包下新建映射文件
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.gsy.dao.StudentMapper"> </mapper>
TeacherMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.gsy.dao.TeacherMapper"> </mapper>
建立新的映射文件记得去核心配置文件中注册
<mappers> <!-- 使用class或扫描包的方式注册mapper要求 接口和对应的mapper配置文件在同一个包下且同名--> <mapper class="com.gsy.dao.TeacherMapper"/> <mapper class="com.gsy.dao.StudentMapper"/> </mappers>
2.多对一
①按结果嵌套处理
<select id="getStudents2" resultMap="StudentTeacher2"> select s.id sid,s.name sname,t.name tname from student s,teacher t where s.tid = t.id </select> <resultMap id="StudentTeacher2" type="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <association property="teacher" javaType="Teacher" > <result property="name" column="tname"></result> </association> </resultMap>
②按查询嵌套处理
<select id="getStudents" resultMap="StudentTeacher"> select * from mybatis.student </select>
<resultMap id="StudentTeacher" type="Student"> <result property="id" column="id"></result> <result property="name" column="name"></result> <!-- 复杂属性: 对象:association 集合:collection --> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association> </resultMap>
<select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{id} </select>
测试:
第一种方式的结果

第二种方式的结果

第一种方式 teacher 的 id=0 的原因是 在
<association property="teacher" javaType="Teacher" > <result property="name" column="tname"></result> </association>
中 没有将 查询字段 tid 映射到teacher实体类的属性id,加上就能查到teacher的id了。
3.一对多
环境搭建与上面类似,稍作修改即可
- 将实体类Student中的teacher属性改为 tid
- 在实体类Teacher中新增属性 List<Student> students
- TeacherMapper.java
public interface TeacherMapper { Teacher getTeacherById(@Param("tid") int id); Teacher getTeacherById2(@Param("tid") int id); }
①按结果嵌套处理
<select id="getTeacherById" resultMap="TeacherStudent"> select t.id tid,t.name tname,s.id sid,s.name sname from student s,teacher t where tid=s.tid and tid= #{tid} </select> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--javaType=""指定属性的类型 集合中的泛型信息,我们使用ofType获取--> <collection property="students" ofType="Student" > <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap>
②按查询嵌套处理
<select id="getTeacherById2" resultMap="TeacherStudent2"> select * from teacher where id = #{tid} </select> <resultMap id="TeacherStudent2" type="teacher"> <result property="id" column="id"/> <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/> </resultMap> <select id="getStudentByTeacherId" resultType="Student" > select * from student where tid = #{tid} </select>
两种方式结果都为:
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1),
Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
4.小结
-
关联-association 【多对一】
-
集合-collection 【一对多】
- javaType & ofType
3.1 javaType 用来指定实体类中属性的类型
3.2 ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型
注意点:
- 保证SQL的可读性,尽量保证通俗易懂
- 注意一对多和多对一中,属性名和字段的问题
- 如果问题不好排查错误,可以使用日志,建议使用log4j
八、动态SQL
开始之前搭建好环境
1.数据库
CREATE TABLE `blog`( `id` VARCHAR(50) 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
2.实体类 Blog.java
@Data @Alias("Blog") @AllArgsConstructor @NoArgsConstructor public class Blog { private String id; private String title; private String author; private Date createTime;//属性名和字段名不一致 private int view; }
3.BlogMapper.java
public interface BlogMapper { int addBlog(Blog blog); List<Blog> queryBlogIF(Map map); }
4.BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.gsy.dao.BlogMapper"> </mapper>
记得在核心配置文件中注册mapper
5.IDutils.java 用于随机生成ID
public class IDutils { public static String getId(){ return UUID.randomUUID().toString().replaceAll("-","");//随机生成id }
1.IF
使用 if 能动态的使用sql
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.gsy.dao.BlogMapper"> <insert id="addBlog" parameterType="Blog"> insert into blog values(#{id},#{title},#{author},#{createTime},#{view}) </insert> <select id="queryBlogIF" parameterType="map" resultType="Blog"> select * from blog where 1=1 <if test="title !=null"> AND title = #{title} </if> <if test="author !=null"> AND author = #{author} </if> </select> </mapper>
2.choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。
针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="queryBlogChoose" parameterType="map" resultType="Blog"> select * from blog <where> <choose> <when test="title != null"> title like #{title} </when> <when test="author != null"> and author like #{author} </when> <otherwise> and view like #{view} </otherwise> </choose> </where> </select>
传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。(只按一个条件查)
若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。
3.trim、where、set
<select id="queryBlogIF" parameterType="map" resultType="Blog"> select * from blog <where> <if test="title != null"> and title = #{title} </if> <if test="author != null"> and author = #{author} </if> </where> </select>
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。
而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。
上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
<update id="updateBlog" parameterType="map"> update blog <set> <if test="title != null"> title = #{title}, </if> <if test="author != null"> author = #{author}, </if> </set> <where> id = #{id} </where> </update>
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
(只会删除多余的逗号不会帮你加上逗号)
或者,你可以通过使用trim元素来达到同样的效果:
<trim prefix="SET" suffixOverrides=","> ... </trim>
注意,我们覆盖了后缀值设置,并且自定义了前缀值。
4.SQL片段
有的时候,我们可能会将一些功能的部分抽取出来,方便复用
1.定义SQL片段
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
2.在需要使用的地方 用<include>标签引用sql片段的id即可
<select id="queryBlogIF" parameterType="map" resultType="Blog"> select * from blog <where> <include refid="if-title-author"></include> </where> </select>
注意事项:
- 最好基于单表来定义SQL片段
- 不要在SQL片段中存在where标签
5.foreach
<select id="selectBlog" parameterType="map" resultType="Blog"> select * from blog <where> <foreach collection="ids" item="id" open="(" separator="or" close=")"> id = #{id} </foreach> </where> </select>
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。
它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!
提示 :
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。
当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。
当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
九、缓存
1.简介
1.什么是缓存(Cache)
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用了从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存
- 减少和数据库的交互次数,较少系统开销,提高系统效率。
3.什么样的数据能使用缓存
- 经常查询而且不经常改变的数据(相反的,不经常查询或者不经常改变的数据不建议使用缓存)
2.Mybatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
3.一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
缓存失效
1.查询不同的东西
2,增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
3.查询不同的Mapper.xml
4.手动清理缓存!
小结:一级缓存默认是开启的,只在一次SqlSession中有效,
也就是拿到连接到关闭连接这个区间段(相当于一个用户不断查询相同的数据,比如不断刷新),一级缓存就是一个map
4.二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存:
工作机制
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中:
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
新的会话查询信息,就可以从二级缓存中获取内容:
不同的mapper查出的数据会放在自己对应的缓存(map)中;
在核心配置文件开启全局缓存
<!-- 显示的开启全局缓存--> <setting name="cacheEnable" value="true"/>

<!-- 在当前Mapper.xml中使用二级缓存--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
问题:
我们需要将实体类序列化(实现Serializable接口),否则就会报错
sqlsession关闭的时候一定要在最后关闭,不能先关闭sqlsession再关闭sqlsession2,这样会导致Cause: org.apache.ibatis.executor.ExecutorException: Executor was closed
小结
只要开启了二级缓存,在同一个Mapper下就有效
所有的数据都会先放在一级缓存中
5.缓存原理
缓存顺序:
- 先看二级缓存中有没有
- 再看一级缓存中有没有
- 查询数据库
注:一二级缓存都没有,查询数据库,查询后将数据放入一级缓存

6.自定义缓存
1.在pom.xml导入ehcache包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>

浙公网安备 33010602011771号