一、mybatis简介
- MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO
二、特点
- 简单易学
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 支持编写动态sql
三、环境配置(入门)
步骤:
- 导入依赖
- 创建实体类
- 创建全局mybatis-config.xml配置文件
- 创建mapper interface,定义查询方法
- 创建mapper.xml配置文件,使用mapper namespace映射mapper interface
- 在mybatis-config.xml中映射绑定mapper.xml文件
- 编写mybatisUtils工具类,用其获得sqlSession
- 编写测试类,测试
1.导入mybatis依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
2.导入mysql数据库依赖
<!--引入mysql驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
3.导入junit测试依赖
<!--引入Junit4-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
4.dao层/mapper层
1.创建userMapper接口
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;
public interface userMapper {
List<User> getListUser();
}
2.创建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">
<mapper namespace="com.mybatis.mapper.userMapper">
<!-- <resultMap id="userRes" type="User">
<result column="pwd" property="passWord"></result>
</resultMap> -->
<select id="getListUser" resultType="com.mybatis.entity.User">
select * from user
</select>
</mapper>
5.问题:
mapper.xml应放在资源目录的根目录下,如果要将其与接口放在一起,则需要配置maven项目的静态资源目录如下:
<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>
6.实体entity
package com.mybatis.entity;
public class User {
private int Id;
private String userName;
private String passWord;
public void setId(int id) {
Id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
@Override
public String toString() {
return "User{" +
"Id=" + Id +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
'}';
}
}
7.创建utils
package com.mybatis.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try{
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}catch (IOException e){
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
8.创建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 resource="db.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mybatis/mapper/userMapper.xml"/>
</mappers>
</configuration>
9.创建db.properties文件
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.23.100:3306/mybatis
username=root
password=123456
10.创建test类
import com.mybatis.entity.User;
import com.mybatis.mapper.userMapper;
import com.mybatis.utils.MybatisUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class mybatisTest {
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
List<User> userList = mapper.getListUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
11.开启事务(CUID)
1.预编译
mybatis执行sql语句,会首先执行预编译,在进行CUID处理时,必须在执行sql语句后进行提交,否则数据库执行失败
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
mapper.setUser(user);
sqlSession.commit();
sqlSession.close();
}
2.mybatis开启事务自动提交
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
开启后执行事务后,会自动提交
四、基础
1.mybatis执行流程(简易)
2.属性名与列名不一致问题
解决:在mapper.xml文件中,使用resultMap进行映射
<resultMap id="userRes" type="com.mybatis.entity.User">
<result column="pwd" property="passWord"></result>
</resultMap>
<select id="getListUser" resultMap="userRes">
select * from user
</select>
<!-- column 对应数据库中的字段,property 对应类中的属性->
3.类别名
在mapper.xml文件中使用全类名比较麻烦,可在全局mybatis-config.xml中对类起别名
1.mybatis-config.xml
<typeAliases>
<typeAlias type="com.mybatis.entity.User" alias="User"/>
</typeAliases>
2.mapper.xml
<resultMap id="userRes" type="User">
<result column="pwd" property="passWord"></result>
</resultMap>
<select id="getListUser" resultMap="userRes">
select * from user
</select>
4.resultMaps查询
1.mapper接口添加方法
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;
public interface userMapper {
List<User> getListUser();
User getUser(Map map);
}
2.mapper.xml添加sql语句
<select id="getUser" resultMap="userRes" parameterType="map">
select * from user where id = #{id}
</select>
<!--parameterType声明参数类型 -->
3.test类
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("id",1);
User user = mapper.getUser(map);
System.out.println(user);
sqlSession.close();
}
5.分页
- mybatis分页的本质底层使用jdbc和mysql的limit实现;
- mybatis本质是对jdbc的封装,比起其他orm框架更贴近底层更贴近
1.resultMap实现
1.mapper.xml添加select语句
<select id="getListUser" resultMap="userRes" parameterType="map">
<bind name="startPage" value="(startIndex-1)*pageSize"/>
select * from user limit #{startPage},#{pageSize}
</select>
2.mapper接口
public interface userMapper {
//分页
List<User> getListUser(Map map);
//查询指定id
User getUser(Map map);
}
3.test测试方法
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("startIndex",2);
map.put("pageSize",2);
List<User> userList = mapper.getListUser(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
2.ByRowBounds分页
1.mapper接口
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;
public interface userMapper {
//分页
List<User> getListUser(Map map);
//RowBounds分页
List<User> getListByRowBounds();
//查询指定id
User getUser(Map map);
}
2.mapper.xml文件
<select id="getListByRowBounds" resultMap="userRes">
select * from user
</select>
3.test方法
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(2,2);
List<User> userList = sqlSession.selectList("com.mybatis.mapper.userMapper.getListByRowBounds",null,rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
6.日志系统
1.STDOUT_LOGGING日志实现
mybatis内置标准日志
mybatis-config.xml
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
2.Log4j日志实现
1.mybatis-config.xml
<settings>
<setting name="logImpl" value="Log4j"/>
</settings>
2.导入log4j依赖
<!--log4j依赖-->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3.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=[%p][%d{yy-MM-dd}][%c]%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.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
五、使用注解
1.为什么使用注解开发
在以往的开发中需要配置大量的xml配置文件,尤其是SSM框架,为了简化开发,引入了注解开发
-
还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置
-
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,需要做一些很复杂的操作,最好用 XML 来映射语句。
在开发中注解和xml也可混合使用,在mybatis中使用xml和注解开发,可以由程序员自己自定义原生sql,这和habinate是不同的,这使得mybatis更具灵活性,但前提是开发者能够写出高效的sql
面试重点:
- mysql引擎
- InnoDB底层原理
- 索引
- 索引优化
2.注解开发
1.绑定接口
<mappers>
<mapper class="com.mybatis.mapper.userMapper"/>
<mapper resource="com/mybatis/mapper/userMapper.xml"/>
</mappers>
<!-- 绑定接口,接口位置要在xml文件之前(也可能是两种文件名相同时,遵循此规则,否则报错)-->
2.mapper接口
@Select("select * from user")
List<User> getList();
3.测试
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
List<User> userList = mapper.getList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
在单独使用注解开发时mapper.xml文件将不再需要
4.注解传参(增删改查)
1.修改mapper接口
@Select("select * from user where id = #{id}")
User getUser(int id);
2.修改测试类
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
User use = mapper.getUser(1);
System.out.println(user);
sqlSession.close();
}
3.@Param注解使用
@Select("select * from user where id = #{uid}")
List<User> getList(@Param("uid") int id);
注意:此时sql中的参数#{uid},必须和@Param(“uid”)注解中的保持一致,否则报错
六.一对多
在一对多查询时有两种方式:
- 按查询嵌套处理
- 按结果嵌套处理
1.环境准备:
1.student.java
package com.mybatis.entity;
public class Student {
private int id;
private String name;
private String pwd;
private Teacher teacher;
public Student() {
}
public Student(int id, String name, String pwd, Teacher teacher) {
this.id = id;
this.name = name;
this.pwd = pwd;
this.teacher = teacher;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
", teacher=" + teacher +
'}';
}
}
2.teacher.java
package com.mybatis.entity;
public class Teacher {
private int id;
private String name;
private String pwd;
public Teacher() {
}
public Teacher(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
2.按查询嵌套处理
<!--思路:
1.查找student
2.做结果集映射,嵌套teacher子查询
3.mybatis会自动根据student表的外键取关联teacher
4.#{uid}可以是任意值,最好使用uid
-->
<select id="getStudentList" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<association property="teacher" column="uid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{uid}
</select>
3.按结果嵌套处理
<!--
思路:
1.首先根据sql去查询结果
2.结果集嵌套映射
-->
<select id="getStudentList2" resultMap="StudentTeacher2">
SELECT s.id sid,s.name sname,s.pwd spwd,t.id tid,t.name tname,t.pwd tpwd
FROM teacher t join student s
where t.id = s.uid
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="pwd" column="spwd"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<result property="pwd" column="tpwd"/>
</association>
</resultMap>
以上两种方式均依赖association,association标签来解决一对一的关联查询;
查询无误,则结果如上;
注意:如果关联查询的javabean中的属性名,数据库表中字段名一致,则需要做别名处理,即结果集映射,如上按结果嵌套处理代码;
七.多对一
1.环境准备
1.student.java
package com.mybatis.entity;
public class Student {
private int id;
private String name;
private String pwd;
private int tid;
public Student() {
}
public Student(int id, String name, String pwd, int tid) {
this.id = id;
this.name = name;
this.pwd = pwd;
this.tid = tid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
", tid=" + tid +
'}';
}
}
2.teacher.java
package com.mybatis.entity;
import java.util.List;
public class Teacher {
private int id;
private String name;
private String pwd;
List<Student> students;
public Teacher() {
}
public Teacher(int id, String name, String pwd, List<Student> students) {
this.id = id;
this.name = name;
this.pwd = pwd;
this.students = students;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
", students=" + students +
'}';
}
}
1.按结果嵌套处理
<select id="getTeacher" resultMap="TeacherStudent">
select t.id tid,t.name tname,t.pwd tpwd,s.id sid,s.name sname,s.pwd spwd,s.uid stid from teacher t
join student s
on t.id = s.uid
where t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<result property="pwd" column="tpwd"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="pwd" column="spwd"/>
<result property="tid" column="stid"/>
</collection>
</resultMap>
2.按查询嵌套处理
<!-- 按查询嵌套处理-->
<select id="getTeacher2" 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="getStudent" column="id" />
</resultMap>
<select id="getStudent" resultMap="Student1">
select * from student where uid = #{id}
</select>
<resultMap id="Student1" type="Student">
<result property="tid" column="uid"/>
</resultMap>
八、动态SQL
1.什么是动态sql
- 动态 SQL 是 MyBatis 的强大特性之一。彻底摆脱JDBC sql语句拼接痛苦;
- MyBatis 显著地提升了动态sql的易用性;
2.动态sql特点:
- 动态sql根据不同条件,生成不同的sql语句
- 动态sql就是在sql语句中使用逻辑判断语句if、where、set、choose、otherwise、foreach
3.动态sql-if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
if语句会判断#{title}是否为空,如果为空将舍去 AND title like #{title} ;
多条件查询:
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
4.动态sql-choose、when、otherwise
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
choose、when、otherwise作用如:开发语言中的switch语句,如果有满足test条件的将不再继续向下执行,如果都不满足将指向****标签的默认语句
5.动态sql-where
注意:
- 我们不想使用所有的条件,而只是想从多个条件中选择一个使用
- 前面几个例子已经合宜地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
SELECT * FROM BLOG
WHERE
这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
这也会导致查询失败;
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
where会智能的判断:如果第一个条件不成立,自动将下一条成立的条件之前的 and 或者 or 删除;尽可能保证sql语句的正确;
6.动态sql-set
动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
7.动态sql-trim
trim 元素可以来自定义set、where标签
1.set元素等价的自定义 trim 元素
<trim prefix="SET" suffixOverrides=",">
...
</trim>
suffixOverrides 定义覆盖的后缀值
prefix属性中指定的内容
2.where 元素等价的自定义 trim 元素
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容
8.foreach
-
foreach标签用于对集合进行遍历(尤其是在构建 IN 条件语句的时候)
-
foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量
-
它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符
-
你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值
<select id="selectPostIn" resultType="domain.blog.Post"> SELECT * FROM POST P WHERE ID in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} </foreach> </select>
9.动态sql-sql片段
在mybatis的mysql中可以提取公共的使用频率较高的sql片段
<!--定义sql片段-->
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<!--将sql片段引入sql语句,构建sql语句-->
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
sql标签定义了sql片段的id
include向sql语句中引入sql片段
refid :要引入的sql片段的id
10.动态sql-bind
bind
元素允许你在 OGNL 表达式以外创建一个变量,并可作用于sql语句,以及上下文
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
九、缓存机制
1.什么是缓存
用户访问访问数据库,对数据库的操作有两种:数据的读写;在用户量较大的情况下,用户将数据库将承受较大的访问压力;在对用户行为分析发现,用户的操作读业务70%~80%,也就是大多数情况下,用户只是查询数据;于是就产生了读写分离的架构,读写分离的架构实现有多种方式;
例:
在业务服务器与数据库之间加入缓存服务器,用户的读操作会首先访问缓存服务器,缓存服务器中没有要查询的数据,再去访问数据库,存在则直接返回数据;
根据上述例子看出:
- 缓存就是一块存储区域或者专门的设备
- 数据暂存再缓存中,会大幅度降低数据库的访问压力,系统的访问速度
- 缓存中适合存放经常查询的、不变的数据,不适合经常修改的数据
2.Mybatis缓存
mybatis提供了一级缓存、二级缓存、自定义缓存接口
-
一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
-
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。
-
对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
3.一级缓存
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存;
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
User user1 = mapper.getUserById(1);
System.out.println("--------------------------");
User user2 = mapper.getUserById(1);
System.out.println(user1==user2);
sqlSession.close();
}
结果返回ture,会发现user1与user2的地址是相等的,也就是说user1、user2在内存中的地址是相等的,即一个对象(对查询而言)
在控制台日志当中发现只执行了一次sql语句查询,也就是说在第一次查询时,mybatis将user1放在了缓存中,在第二次访问时并没有访问数据库,而是访问的缓存
@Test
public void test1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
userMapper mapper = sqlSession.getMapper(userMapper.class);
User user1 = mapper.getUserById(1);
user1.setUserName("zzzz");
mapper.updateUser(user1);
System.out.println("--------------------------");
User user2 = mapper.getUserById(1);
System.out.println(user1==user2);
sqlSession.close();
}
结果返回false,会发现两次查询都访问了数据库
结论:
- mybatis默认开启一级缓存,会将查询会将结果放入缓存
- 增删改会破坏缓存,mybatis将重新访问数据库,再次查询时刷新缓存数据库
- 一级缓存在**sqlSession.open()**自动开启
- 一级缓存在**sqlSession.close()/Clear()**自动关闭
4.二级缓存
1.二级缓存显式开启
在mybatis-conf.xml全局配置文件中配置:(默认是开启的)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:(二级缓存基于namespace,与****当前所在的mapper才会开启缓存
<cache/>
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
注意:
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
2.二级缓存配置
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突
缓存清除策略:(默认的清除策略是 LRU)
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
-
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
-
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
-
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
-
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
总结:
- 当一级缓存close()后,数据会转存在二级缓存中
- 实体类要实现序列化结构,负责会报错
5.自定义缓存-ehcache
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为
导入ehcache依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
在mapper.xml文件中引入
<cache type="org.mybatis.caches.ehcache"/>
即可在mybatis中使用ehcache,也可在ehcache.xml文件中配置ehcache
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
当前市场使用最热度最高的缓存时redis