目录
前言
简介:MyBatis,封装JDBC,负责访问数据库,完成持久化操作
Mybatis特性
- Mybatis是支持定制化SQL、存储过程以及高级映射的优秀持久层框架
- Mybatis避免了几乎所有JDBC代码和手动设置参数以及获取结果集
- Mybatis可以使用简单的XML或注解用于配置和原始映射,将接口和java的POJO(plain old java objects,普通的java对象)映射成数据库中的记录
- Mybatis是一个半自动的ORM(object relation mapping)框架
ORM对象关系映射
- 对象:java的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java概念 | 数据库概念 |
类 | 表名 |
属性 | 字段 |
对象 | 行 |
理解:mybatis中通过sql语句查询到的结果与类中的中的属性一一映射;形成一个对象(如果查询到的属性少于pojo对象中的属性,那么pojo对象中多出的属性为默认值)resultType或resultMap中对应的返回值类型与pojo中的类一一映射。
mybatis的使用
用到的依赖
<!--依赖集-->
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!--单元测试依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mysql数据库依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--添加日志依赖-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- lombok依赖包 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
</dependencies>
jdbc.properties
#放前缀就是为了避免重名
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
jdbc.username=root
jdbc.password=root
mybatis初级创建所使用的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--mybatis核心配置文件-->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--注意:mybatis核心配置文件中的标签必须按照指定的顺序配置
properties,setting,typeAliases,typeHandLers,
objectFactory,objectWrapperFactory,reflectorFactory,
plugins,environments,databaseIdProvider,mappers
-->
<!--引入properties文件,此后就可以在当前文件中使用${key}的方式来访问value-->
<properties resource="jdbc.properties"/>
<settings>
<!--驼峰命名规则,自动将_映射成驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启延迟加载后没用到的子查询不会开启;(aggressiveLazyLoading)完整加载设置为false-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/><!--默认为false-->
</settings>
<!--类型别名,我们可以根据具体的类型设置一个简短的名字-->
<typeAliases>
<!--表示如果我们想要访问cn.tedu.mybatis.pojo.User的时候我们通过AliasUser就可以表示-->
<!--注意:alias也可以省略,若省略别名为最后面的类名,并且不区分大小写-->
<!--<typeAlias type="cn.tedu.mybatis.pojo.User" alias="AliasUser"></typeAlias>(以类的方式设置别名)-->
<!--这样设置包名啊,那么该包下的所有类都有它自己的别名,就是他自己的类名且类名不区分大小写-->
<package name="cn.tedu.mybatis.pojo"/><!--以包的方式设置别名-->
</typeAliases>
<!--配置连接数据库的环境,default表示默认环境,environments表示多个环境,可以在多个该标签里面设置多个environment标签-->
<!--默认为开发环境-->
<environments default="development">
<environment id="development">
<!--事务管理器,类型jdbc-->
<!--事务管理器type两种值,JDBC:表示使用JDBC中原生的事务管理方式。MANAGED:被管理-->
<transactionManager type="JDBC"/>
<!--JDBC提供的数据源接口就叫dataSource-->
<!--数据源的作用,帮助我们管理数据库连接-->
<!--type="POOLED"/"UNPOOLED"/"JNDI"-->
<!--POOLED:表示使用数据库连接池,UNPOOLED:表示不适用数据库连接池,JNDI:表示使用上下文中的数据源-->
<dataSource type="POOLED">
<!--驱动类-->
<property name="driver" value="${jdbc.driver}"/>
<!--连接数据库地址-->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入mybatis的映射文件,映射文件主要写sql语句的,注意,里面可以写多个mapper标签-->
<mappers>
<!--映射资源文件(写sql的),这里映射文件我要放在mapper目录下的UserMapper.xml-->
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
通过包名来引入当前文件,使用时有要求,
1要求映射文件所在的包一定要和mapper接口所在的包名一样
2映射文件的名字一定要和mapper接口的名字保持一致
-->
<package name="cn.tedu.mybatis.mapper"/>
</mappers>
</configuration>
mybatis初级创建使用的映射文件
<?xml version="1.0" encoding="UTF-8" ?><!--xml文件的声明-->
<!--约束当前文件中可以使用的标签-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间要和接口的全接口名保持一致——包名.接口名(不可设置别名)-->
<mapper namespace="cn.tedu.mybatis.mapper.UserMapper">
<!--接口里的方法名要和此文件中的id保持一致-->
<insert id="insertUser">
insert into t_user values(null ,'admin','123456',23,'男','12345@qq.com')
</insert>
<update id="updateUser">
update t_user set username='root',password='123' where id=3
</update>
<delete id="deleteUser">
delete from t_user where id=3
</delete>
<!--注意查询语句有返回值-->
<!--resultType作为返回值类型映射只支持对象的属性名和字段名一致的映射,resultMap表示自定义映射,万能-->
<!--
resultType:设置结果集类型,即查询的数据要转换为的java类型
resultMap:自定义映射,处理多对一或一对多的映射关系
-->
<!--必须写全类名,不然它不知道是哪个类,这里我用了别名,但是下面的我不用了-->
<select id="getUserById" resultType="User">
select * from t_user where id=5
</select>
<select id="getAllUser" resultType="cn.tedu.mybatis.pojo.User">
select * from t_user;
</select>
</mapper>
打印日志配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!--log4j.xml文件内,名字不可改变-->
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!--日志配置文件-->
<log4j:configuration>
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n"/>
</layout>
</appender>
<logger name="java.sql">
<level value="debug"/>
</logger>
<logger name="org.apache.ibatis">
<level value="info"/>
</logger>
<root>
<level value="debug"/>
<appender-ref ref="STDOUT"/>
</root>
</log4j:configuration>
mybatis初级使用测试案例
@Test
public void testInsert() throws Exception {
//获取核心配置文件的输入流
InputStream is = Resources.getResourceAsStream("mybatis-config-xml");
//获取sqlsessionFactoryBuilder对象
SqlSessionFactoryBuilder sfb = new SqlSessionFactoryBuilder();
//获取sqlsessionFactory对象
SqlSessionFactory sf = sfb.build(is);
//获取sqlsession对象——mybatis提供的操作数据可得对象(这里面的true表示自动提交事务,就是不用写后面的commit方法了)
SqlSession sqlSession = sf.openSession(true);
//获取UserMapper的代理实现类对象(通过代理模式来创建UserMapper的代理实现类)
//通过当前mapper接口的全类名找到当前的映射文件
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//通过调用方法,找到映射文件中的方法
int i = mapper.insertUser();
//提交事务
//sqlSession.commit();
//sqlSession.rollback();回滚事务
System.out.println(i);
//关闭会话
sqlSession.close();
}
日志级别
debug(调试)<info(通知)<warn(警告)<error(错误)<fatal(致命)
注意:配置日志级别,这样在打印日志的时候只会打印相同级别以及高于自己配置的日志级别日志
lombok依赖
作用:自动生成Get/Set/toString/无参构造/全参构造/equals/hashcode等方法
注解
- @Data:动态生成get/set/toString等方法
- @Accessor(chain=true):开启链式加载,重写所有的set方法
- @NoArgsConstructor:动态生成无参构造
- @AllArgsContructor:动态生成全参构造
properties文件语法
- 注释用#
- 数据结构类型:key=value(特别注意不能有空格)
- 字符集编码:程序读取文件时默认采用ISO-8859-1编码
- 弊端:所有的key都必须完整不能缩进
mybatis传参
mybatis获取参数值的两种形式
- mybatis获取参数值两种方式:${}和#{}
- ${}的本质就是字符串拼接sql,#{}本质就是占位符赋值
- ${}使用字符串拼接sql,若为字符串类型或日期类型的字段赋值时需要在${}外面或里面手动加单引号;但是使用#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动加单引号,不用手动加
单值查询
测试:List<User> user = mapper.getUserByUsername("admin"); (mapper为接口代理对象)
接口:List<User> getUserByUsername(String username);
映射文件:select * from t_user where username='${username}'; (为字符串拼接方式)
select * from t_user where username=#{username}; (为占位符方式)
多值查询
第一种:使用@Param注解(本质map)
测试:User user = mapper.checkLogin("admin","123456");
接口:User checkLogin(@Param("name") String username,@Param("pwd") String password);
配置文件:select * from t_user where username=#{name} and password=#{pwd}
第二种:使用map集合
测试:
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("name", "admin");
map.put("pwd", "123456");
User user = mapper.checkLoginByMap(map);
接口:User checkLoginByMap(Map<String,Object> map);
配置文件:select * from t_user where username='${name}' and password=#{pwd};
第三种:使用实体类封装
测试:
User user = new User(null,"root","123456",33,"女","123@qq.com");(我有全参构造)
int i = mapper.insertUser(user);
接口:int insertUser(User user);
配置文件:insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
mybatis的各种查询功能
查询多条数据
测试:List<User> allUser = mapper.getAllUser();
接口:List<User> getAllUser();
配置文件:
<select id="getAllUser" resultType="user">
select * from t_user;
</select>
注意:若sql查询的结果为多条时,一定不能以实体类型作为方法的返回值,否则会抛出异常,若查询结果为1条时,此时可以使用实体类型或list集合类型作为方法的返回值
查询结果为integer类型(不为实体类)的数据
测试:Integer count = mapper.getCount();
接口:Integer getCount();
配置文件:
<select id="getCount" resultType="java.lang.Integer">
select count(*) from t_user;
</select>
注意:这里的resultType返回值也可以直接写Integer或int(忽略大小写——本质别名)
查询返回值为map集合的单个数据
测试:Map<String, Object> userByIdToMap = mapper.getUserByIdToMap(1);
接口:Map<String,Object> getUserByIdToMap(@Param("id") Integer id);
配置文件:
<select id="getUserByIdToMap" resultType="map">
select * from t_user where id=#{id}
</select>
理解:把通过id查到的数据用map集合进行封装(一条数据转化为一个map)
查询返回值为map集合的多个数据
测试:List<Map<String, Object>> allUserToMap = mapper.getAllUserToMap();
接口:List<Map<String,Object>> getAllUserToMap();
配置文件:
<select id="getAllUserToMap" resultType="map">
select * from t_user;
</select>
测试:Map<String, Object> allUserToMap = mapper.getAllUserToMap();
接口:
@MapKey("id")——把查询到的id作为键,把查询到的对象(也就是一条一条的数据)作为值
Map<String,Object> getAllUserToMap();
配置文件:
<select id="getAllUserToMap" resultType="map">
select * from t_user;
</select>
特殊操作
模糊查询
测试:List<User> a = mapper.getUserByLike("a");
接口:List<User> getUserByLike(@Param("m") String m);
配置文件:
方式1:
<select id="getUserByLike" resultType="user">
select * from t_user where username like "%"#{m}"%";
</select>
方式2:
<select id="getUserByLike" resultType="user">
select * from t_user where username like "%${m}%";
</select>
测试:List<User> a = mapper.getUserByLike("%a%");
接口:List<User> getUserByLike(@Param("m") String m);
配置文件:
<select id="getUserByLike" resultType="user">
select * from t_user where username like #{m};
</select>
注意:#{内容}若写在字符串里面则会被当作字符串的一部分,不会被当作占位符
批量删除
测试:int i = mapper.deleteMoreUser("1,7");
接口:int deleteMoreUser(@Param("ids") String ids);
配置文件:
方法1:
<delete id="deleteMoreUser">
delete from t_user where id in(${ids})
</delete>
动态设置表名
测试:List<User> t_user = mapper.getUserList("T_user");
接口:List<User> getUserList(@Param("tableName") String tableName);
配置文件:
<select id="getUserList" resultType="user">
select * from ${tableName}
</select>
主要原因:表名是不能加单引号的,所以不能用#{},因为它默认会为值添加单引号
获取自增的主键
测试:
User user = new User(null, "lili", "123456", 23, "男", "12@qq.com");
mapper.insertUser(user);——这里我没为id赋值,但是输出却有id值
接口:void insertUser(User user);
配置文件:
<!--获取自增的主键,keyProperty:把获取到自增的主键存储到我们传输过来的实体类对象的对应属性中-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null ,#{username},#{password},#{age},#{gender},#{email})
</insert>
处理查询字段名与属性名不一致
起别名解决
测试:Emp empByEmpId = mapper.getEmpByEmpId(1);
接口:Emp getEmpByEmpId(@Param("empId") Integer empId);
配置文件:
<select id="getEmpByEmpId" resultType="emp">
select emp_id empId,emp_name empName,age,gender from t_emp where emp_id=#{empId}
</select>
注意:这里起的别名是对应实体类(Emp)中属性的名称
满足驼峰则开启驼峰命名
在mybatis-config.xml核心配置文件中设置如下(注意设置标签的顺序问题)
<settings>
<!--驼峰命名规则,自动将_映射成驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
自定义映射解决
测试:Emp empByEmpId = mapper.getEmpByEmpId(1);
接口:Emp getEmpByEmpId(@Param("empId") Integer empId);
配置文件:
<select id="getEmpByEmpId" resultMap="empResultMap">
select * from t_emp where emp_id=#{empId}
</select>
<!--id:resultMap配置的id(随便写都行)
type:返回值的类型-->
<resultMap id="empResultMap" type="emp">
<!--处理主键映射-->
<!--将字段里的emp_id与对象中的empId映射-->
<id column="emp_id" property="empId"></id>
<!--普通字段的映射-->
<result column="emp_name" property="empName"></result>
</resultMap>
多对一查询
//所用到的实体类
public class Emp {
private Integer empId;
private String empName;
private Integer age;
private String gender;
private Dept dept;
}
级联
<select id="getEmpAnddeptByEmpId" resultMap="empAndDeptResultMap">
select t_emp.*,t_dept.* from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where t_emp.emp_id=#{empId}
</select>
<resultMap id="empAndDeptResultMap" type="emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
注意:dept表为emp表的内部属性
association
<select id="getEmpAnddeptByEmpId" resultMap="empAndDeptResultMap">
select t_emp.*,t_dept.* from t_emp left join t_dept on t_emp.dept_id=t_dept.dept_id where t_emp.emp_id=#{empId}
</select>
<resultMap id="empAndDeptResultMap" type="emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!--处理多对一映射关系,必须是处理实体类型中的属性-->
<!--javaType用来设置当前属性的类型-->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
注意:association标签里还可以有autoMapping属性,若此属性设置为true,则可以使被封装的属性与和属性名称相同的字段进行自动映射(autoMapping设置为true时,若映射的字段和对象的属性一致时则可以省略不写)
分步查询
一次查询:
<select id="getEmpAndDeptByStep" resultMap="empAndDeptByStepResultMap">
select * from t_emp where emp_id=#{empId}
</select>
<resultMap id="empAndDeptByStepResultMap" type="emp" autoMapping="true">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<!--select属性表示二次查询sql的id,column表示从第一次查询结果中返回的某个字段作为二次查询的条件-->
<!--fetchType属性在全局开启了延迟加载情况下设置某一条语句立即加载-->
<association property="dept" select="cn.tedu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id" autoMapping="true" fetchType="eager">
</association>
</resultMap>
二次查询:
<select id="getEmpAndDeptByStepTwo" resultType="dept">
select * from t_dept where dept_id = #{deptId}
</select>
一对多查询
public class Dept {
private Integer deptId;
private String deptName;
private List<Emp> emps;
}
collection
<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.dept_id=t_emp.dept_id where t_dept.dept_id=#{deptId}
</select>
<resultMap id="deptAndEmpResultMap" type="dept" autoMapping="true">
<id column="dept_id" property="deptId"></id>
<!--ofType:设置当前集合中的类型-->
<collection property="emps" ofType="emp" autoMapping="true">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
</collection>
</resultMap>
注意:不管映射不映射,主键一定要写上
分步查询
一次查询:
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
select * from t_dept where dept_id=#{deptId}
</select>
<resultMap id="deptAndEmpResultMapByStep" type="dept" autoMapping="true">
<id column="dept_id" property="deptId"></id>
<collection property="emps" column="dept_id"
select="cn.tedu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo" autoMapping="true">
<id column="emp_id" property="empId"></id>
</collection>
</resultMap>
二次查询:
<select id="getDeptAndEmpByStepTwo" resultType="emp">
select * from t_emp where dept_id=#{deptId}
</select>
动态sql
Mybatis框架的动态sql技术是一种根据特定条件动态拼接sql语句的功能,它存在的意义就是为了解决拼凑sql语句字符串时的痛点问题
if和where和trim标签
测试:
Emp emp = new Emp(null, "张三", 20, "男");
List<Emp> empByCondition = mapper.getEmpByCondition(emp);
接口:List<Emp> getEmpByCondition(Emp emp);
配置:
方法1:
<select id="getEmpByCondition" resultType="emp">
select * from t_emp <where>
<if test="empName!=null and empName!=''">
emp_name=#{empName}
</if>
<if test="age!=null and age!=''">
and age=#{age}
</if>
<if test="gender!=null and gender!=''">
and gender=#{gender}
</if>
</where>
</select>
方法2:
<select id="getEmpByCondition" resultType="emp">
select * from t_emp where 1=1
<if test="empName!=null and empName!=''">
and emp_name=#{empName}
</if>
<if test="age!=null and age!=''">
and age=#{age}
</if>
<if test="gender!=null and gender!=''">
and gender=#{gender}
</if>
</select>
方法3:
<select id="getEmpByCondition" resultType="emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="empName!=null and empName!=''">
emp_name=#{empName} and
</if>
<if test="age!=null and age!=''">
age=#{age} and
</if>
<if test="gender!=null and gender!=''">
gender=#{gender}
</if>
</trim>
</select>
注意:
- if标签作用:满足sql标签的则进行sql语句的拼接,不满足的则不进行sql语句的拼接
- where标签功能:自动生成where关键字、处理数据为null时and(数据前面的and,内容后面的and无法去掉)多余问题、若当前标签里没有任何一个标签成立,那么where标签没有任何功能
- 代码中trim标签解释:最前面加where,在最后面去掉and
set标签
测试:
Emp w = new Emp(2, "王五", 23, null);
int i = mapper.updateEmp(w);
接口:int updateEmp(Emp emp);
配置文件:
<update id="updateEmp">
update t_emp
<set>
<if test="empName!=null and empName!=''">emp_name=#{empName},</if>
<if test="age!=null and age!=''">age=#{age},</if>
<if test="gender!=null and gender!=''">gender=#{gender}</if>
</set>
where emp_id=#{empId}
</update>
注意:set标签主要处理多余的逗号问题,也会生成set关键字
choose、when、otherwise标签
测试:
Emp emp = new Emp(null, "张三", 20, "男");
List<Emp> empByChoose = mapper.getEmpByChoose(emp);
接口:List<Emp> getEmpByChoose(Emp emp);
配置:
<select id="getEmpByChoose" resultType="emp">
select * from t_emp
<where>
<choose>
<when test="empName!=null and empName!=''">emp_name=#{empName}</when>
<when test="age!=null and age!=''">age=#{age}</when>
<otherwise>gender=#{gender}</otherwise>
</choose>
</where>
</select>
注意:
- when至少设置1个,otherwhile最多设置1个
- 选择语句最后只有1个分支被执行
forearch标签
批量添加员工
测试:
Emp emp = new Emp(null, "张三", 20, "男");
Emp emp1 = new Emp(null, "张四", 21, "男");
Emp emp2 = new Emp(null, "张五", 22, "男");
List<Emp> emps = Arrays.asList(emp, emp1, emp2);
mapper.insertMoreEmp(emps);
接口:void insertMoreEmp(@Param("emps") List<Emp> emps);
配置文件:
<!--collection里面传@Param里面对应的键,表示要循环的集合或数组;item里面的属性表示用什么表示集合里面的数据;separator表示循环所用分隔符-->
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.gender},null)
</foreach>
</insert>
注意:如果不写@Param注解,那么如果你的参数是一个list集合,则mybatis会把他以list为键,后以list集合为值放入map集合中;若你的参数是一个数组的话,则mybatis也会把它放入map中,以Array为键,以其参数值为值
批量删除员工
测试:
Integer[] integers = {5,6,7};
mapper.deleteMoreEmp(integers);
接口:void deleteMoreEmp(@Param("empIds") Integer[] empIds);
配置文件:
方式1:
<delete id="deleteMoreEmp">
delete from t_emp where emp_id in
(
<foreach collection="empIds" item="empId" separator=",">
#{empId}
</foreach>
)
</delete>
注意:foreach里面还有2个属性,分别为open和close,分别表示以什么符号开始和以什么符号结束(不写括号时使用)
方式2:
<delete id="deleteMoreEmp">
delete from t_emp where
<foreach collection="empIds" item="empId" separator="or">
emp_id=#{empId}
</foreach>
</delete>
sql标签
测试:List<Emp> emps = mapper.selectAll();
接口:List<Emp> selectAll();
配置文件:
<!--sql中的id表示sql片段的标识,include标签表示引用sql,refid与sql中id对应-->
<sql id="empColumn">
emp_id,emp_name,age,gender,dept_id
</sql>
<select id="selectAll" resultType="emp">
select <include refid="empColumn"></include> from t_emp;
</select>
mybatis注解开发
测试:Emp emp = mapper.getById(1);
接口:
@Select("select * from t_emp where emp_id=#{id}")
Emp getById(@Param("id") Integer id);
注意:
- 可以利用@Select、@Insert、@Update、@Delete等注解来代替映射文件的方式进行数据库操作
- 注解和映射文件的方式二选1,映射文件方式为主导
- 注解写法一般只适用于简单操作,关联查询不适用
- 这些注解之间不可以互相通用,因为会涉及到返回值问题
mybatis缓存
mybatis的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据会从缓存中直接获取,不会从数据库重新访问
注意:一级缓存默认开启
测试一级缓存
@Test
public void testGetEmpById(){
SqlSession session = SqlSessionUtils.getSession();
CacheMapper mapper = session.getMapper(CacheMapper.class);
Emp empById = mapper.getEmpById(1);
//清空一级缓存
session.clearCache();
Emp empById1 = mapper.getEmpById(1);
System.out.println(empById+"\n"+empById1);
}
注意:这里我清空一级缓存了,所以会查2次
一级缓存失效的情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession,但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作(会清空缓存)
- 同一个SqlSession两次查询期间手动清空了缓存
mybatis的二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSessionFactory创建的SqlSession,默认开启,但是需要标识
二级缓存开启的条件
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”(注意:这里默认为true,所以不用配置)
- 在映射文件中设置标签<cache/>用于标识(或在接口名上加@CacheNamespace注解)
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类型必须实现序列化接口
二级缓存失效的情况
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
二级缓存测试
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp implements Serializable {
private Integer empId;
private String empName;
private Integer age;
private String gender;
}
接口方法:Emp getEmpById(Integer empId);
<mapper namespace="cn.tedu.mybatis.mapper.CacheMapper">
<cache/>
<select id="getEmpById" resultType="emp">
select * from t_emp where emp_id = #{empId}
</select>
</mapper>
@Test
public void testCache() throws Exception {
InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
//创建第一个sqlsessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp empById = mapper.getEmpById(1);
System.out.println(empById);
//只有将sqlsession关闭之后保存在一级缓存中的数据才会保存在二级缓存中,二级缓存才能生效
sqlSession.close();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp empById1 = mapper1.getEmpById(1);
System.out.println(empById1);
sqlSession1.close();
}
注意:只有将sqlsession关闭之后保存在一级缓存中的数据才会保存在二级缓存中,二级缓存才能够生效
二级缓存相关配置
前言:这些属性可以在cache标签里面或注解@CacheNamespace()里面设置
设置属性
eviction属性:缓存回收策略,默认为LRU
- LRU:最近最少使用的(移除最长时间不被使用的对象)
- FIFO:先进先出(按对象进入缓存的顺序来移除他们)
- SOFT:软引用(移除基于垃圾回收器状态和软引用规则的对象)
- WEAK:弱引用(更积极地移除基于垃圾收集器状态和弱引用规则的对象)
flushInterval属性:刷新间隔,单位毫秒(不然只有关闭一级缓存才能刷新)
默认情况下是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
size属性:引用数目,正整数
代表缓存最多可以存储多少对象,太大容易导致内存溢出
readOnly属性:只读,true/false
- true:只读缓存,会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势
- false:读写缓存,会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
mybatis缓存查询顺序
- 先查询二级缓存,因为二级缓存可能有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,在查询一级缓存
- 若一级缓存也没有命中,则查询数据库
- Sqlsession关闭之后,一级缓存中的数据会写入二级缓存