目录
10.1.3 对一映射方式1:通过关联对象打点调用属性的方式
10.1.4 对一映射方式2:直接引用关联对象的Mapper映射
10.1.5 对一映射方式3:直接引用关联对象的单独查询的方法
13.2 一级缓存:自动开启,SqlSession级别的缓存
八、增删改查中的细节
8.1 插入数据的时候获取自增的id-befor方法
8.1.1 案例准备
(1)添加一张新表
CREATE TABLE `gamerecord` (
`recordId` varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`homeTeamId` int DEFAULT NULL COMMENT '主队ID',
`gameDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '比赛日期',
`score` int DEFAULT NULL COMMENT '得分',
`visitingTeamId` int DEFAULT NULL COMMENT '客队ID',
PRIMARY KEY (`recordId`),
KEY `homeTeamId` (`homeTeamId`),
KEY `visitingTeamId` (`visitingTeamId`),
CONSTRAINT `gamerecord_ibfk_1` FOREIGN KEY (`homeTeamId`) REFERENCES `team` (`teamId`),
CONSTRAINT `gamerecord_ibfk_2` FOREIGN KEY (`visitingTeamId`) REFERENCES `team` (`teamId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
(2)实体类
package com.lxy.pojo;
import java.util.Date;
public class GameRecord {
private String recordId;
private Integer homeTeamId;
private Date gameDate;
private Integer score;
private Integer visitingTeamId;
public String getRecordId() {
return recordId;
}
public void setRecordId(String recordId) {
this.recordId = recordId;
}
public Integer getHomeTeamId() {
return homeTeamId;
}
public void setHomeTeamId(Integer homeTeamId) {
this.homeTeamId = homeTeamId;
}
public Date getGameDate() {
return gameDate;
}
public void setGameDate(Date gameDate) {
this.gameDate = gameDate;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
public Integer getVisitingTeamId() {
return visitingTeamId;
}
public void setVisitingTeamId(Integer visitingTeamId) {
this.visitingTeamId = visitingTeamId;
}
}
(3)创建实体类的mapper接口
package com.lxy.mapper;
import com.lxy.pojo.GameRecord;
public interface GameRecordMapper {
int add(GameRecord record);
}
(4)修改配置文件
添加GameRecordMapper.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.lxy.mapper.GameRecordMapper">
<insert id="add" parameterType="com.lxy.pojo.GameRecord">
<selectKey keyProperty="recordId" order="BEFORE" resultType="java.lang.String">
select uuid()
</selectKey>
INSERT INTO `mybatis`.`gamerecord` (`recordId`, `homeTeamId`, `gameDate`, `score`, `visitingTeamId`)
VALUES (#{recordId}, #{homeTeamId},default, #{score}, #{visitingTeamId})
</insert>
</mapper>
(5)测试类
package com.lxy.test;
import com.lxy.mapper.GameRecordMapper;
import com.lxy.pojo.GameRecord;
import com.lxy.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class GameRecordMapperTest {
private SqlSession sqlSession = MybatisUtil.getSqlSession();
@Test
public void testAdd(){
GameRecordMapper mapper = sqlSession.getMapper(GameRecordMapper.class);
GameRecord record = new GameRecord();
record.setHomeTeamId(1014);
record.setVisitingTeamId(1019);
record.setScore(99);
int add = mapper.add(record);
sqlSession.commit();
System.out.println("add结果"+add);
System.out.println(record.getRecordId());
}
}
使用before方法能为insert方法添加一个recordId,可以在创建对象的时候不传参数
8.2 插入数据的时候获取自增的id-after方法
(1)在TeamMapper接口中添加addAfter方法
package com.lxy.mapper;
import com.lxy.pojo.Team;
import java.util.List;
public interface TeamMapper {
List<Team> queryAll();
Team queryById(Integer teamId);
int add(Team team);
int update(Team team);
int del(Integer teamId);
int addAfter(Team team);
}
(2)在TeamMapper.xml中添加addAfter方法
<?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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<insert id="addAfter" parameterType="com.lxy.pojo.Team">
<selectKey keyProperty="teamId" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
INSERT INTO `team` (`teamName`, `location`, `createTime`)
VALUES (#{teamName}, #{location}, #{createTime})
</insert>
</mapper>
(3)编写测试类
@Test
public void testAddAfter(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Team team = new Team();
team.setTeamName("lxy");
team.setLocation("shanghai");
int i = mapper.addAfter(team);
sqlSession.commit();
System.out.println(i);
System.out.println("新增id:"+team.getTeamId());
}
虽然没添加id,但是使用after方法可以直接获取自动添加后的id
8.3 输入映射
8.3.1 parameterType
parameterType:接口中方法参数的类型,类型必须是完全限定名或别名(稍后讲别名)。该属性非必须,因为Mybatis框架能自行判断具体传入语句的参数,默认值为未设置(unset)。
当sql语句中需要多个参数时,就不能使用parameterType方法了。
(1)首先在TeamMapper接口中添加方法声明
List<Team> queryByRange(int min, int max);
(2)在TeamMapper.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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<!--多个参数:标签中不需要parameterType属性
方式1:通过下标索引的方式
select * from team where teamId >=#{arg0} and teamId <=#{arg1}; 也可以,注意下表索引
细节1:
mybatis3.3版本之前:可以直接写#{0} #{1}
从mybatis3.4开始:#{arg0} #{arg1}... 或者是 #{param1} #{param2}...
细节2:
sql语句中不能使用小于号,使用转义符号替换;大于号没有限制,也可以使用转义符号替换>
> greater than
< less than
-->
<select id="queryByRange" resultType="com.lxy.pojo.Team">
select * from team where teamId >=#{param1} and teamId <=#{param2};
</select>
</mapper>
(3)编写测试方法
package com.lxy.test;
import com.lxy.mapper.TeamMapper;
import com.lxy.pojo.Team;
import com.lxy.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class TeamMapperTest {
private SqlSession sqlSession = MybatisUtil.getSqlSession();
@Test
public void testTeamMapper() {
TeamMapper teamMapper = sqlSession.getMapper(TeamMapper.class);
List<Team> teams = teamMapper.queryAll();
for (Team t : teams) {
System.out.println(t);
}
}
@Test
public void testAddAfter(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Team team = new Team();
team.setTeamName("lxy");
team.setLocation("shanghai");
int i = mapper.addAfter(team);
sqlSession.commit();
System.out.println(i);
System.out.println("新增id:"+team.getTeamId());
}
@Test
public void teamTeamMapper(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
List<Team> teams = mapper.queryByRange(1009, 1019);
teams.forEach(team -> System.out.println(team));
}
}
测试结果:
8.3.2 通过@Param注解(常用!!!)
在方法的形参前面加入@Param("自定义参数名称"),mapper文件中使用#{自定义参数名称}的方式传参。
(1)TeamMapper接口添加如下内容:
List<Team> queryByRange2(@Param("min") Integer min, @Param("max") Integer max);
(2)TeamMapper.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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<!--方式2:通过注解的方式:
#{}中的名称必须与接口的方法中的参数注解@Param()保持一致
select * from team where teamId >=#{param1} and teamId <= #{param2}; 不推荐,但是语法 也是正确的,但是不能使用arg0,arg1......
-->
<select id="queryByRange2" resultType="com.lxy.pojo.Team">
select * from team where teamId >=#{min} and teamId <=#{max};
</select>
</mapper>
(3)添加测试方法
package com.lxy.test;
import com.lxy.mapper.TeamMapper;
import com.lxy.pojo.Team;
import com.lxy.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class TeamMapperTest {
private SqlSession sqlSession = MybatisUtil.getSqlSession();
@Test
public void teamTeamMapper2(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
List<Team> teams = mapper.queryByRange2(1009, 1019);
teams.forEach(team -> System.out.println(team));
}
}
测试结果:
8.3.3 通过map来传递多个参数
Map 集合可以存储多个值,使用Map向 mapper 文件一次传入多个参数。Map 集合使用 String的 key,Object 类型的值存储参数。 mapper 文件使用 # { key } 引用参数值
(1)TeamMapper接口添加如下内容:
List<Team> queryByRange3(Map<String,Object> map);
(2)TeamMapper.xml配置文件中添加如下:
<!--方式3:通过map来传递多个参数:映射文件中的参数占位符必须和map中的String类型的字段名称一样-->
<select id="queryByRange3" resultType="com.kkb.pojo.Team">
select * from team where teamId >=#{min} and teamId <= #{max};
</select>
(3)添加测试方法
package com.lxy.test;
import com.lxy.mapper.TeamMapper;
import com.lxy.pojo.Team;
import com.lxy.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TeamMapperTest {
private SqlSession sqlSession = MybatisUtil.getSqlSession();
@Test
public void teamTeamMapper3(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("min",1009);
map.put("max",1019);
List<Team> teams = mapper.queryByRange3(map);
teams.forEach(team -> System.out.println(team));
}
}
测试结果:
8.3.3 通过pojo类传递多个参数
与map传递多个参数类似,要求映射文件中的参数占位符必须和pojo类中的属性完全一致。
(1)实体类:
package com.lxy.pojo;
public class QueryVO {
private String name;
private Integer min;
private Integer max;
private String location;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getMin() {
return min;
}
public void setMin(Integer min) {
this.min = min;
}
public Integer getMax() {
return max;
}
public void setMax(Integer max) {
this.max = max;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
(2)TeamMapper接口添加如下内容:
List<Team> queryByCondition(QueryVO vo);
(3)TeamMapper.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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<!--方式4:通过pojo类传递多个参数:映射文件中的参数占位符必须和pojo类中的字段完全一致-->
<select id="queryByCondition" resultType="com.lxy.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
and teamName like #{name} and location=#{location}
</select>
</mapper>
(4)测试类添加方法:
@Test
public void testQueryByCondition(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
QueryVO vo = new QueryVO();
vo.setLocation("北京");
vo.setName("%球队%");
vo.setMin(1001);
vo.setMax(1020);
List<Team> teams = mapper.queryByCondition(vo);
teams.forEach(team -> System.out.println(team));
}
测试结果:
8.4 #{} 和 ${}的区别--面试中喜欢出的考题
8.4.1 #{}
#{}:表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。
<select id="queryById" parameterType="int" resultType="com.kkb.pojo.Team">
select * from team where teamId=#{id}
</select>
<!--Mybatis执行的时候是:
String sql="select * from team where teamId=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,1001);
where teamId=? 实际就是 where teamId=#{id}
ps.setInt(1,1001) 中的1001会替换#{id}
-->
8.4.2 ${}
${}:表示字符串原样替换,通知Mybatis 使用$包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${}的内容连接起来。一般用在替换表名,
列名,不同列排序等操作。例如:根据球队名称,球队位置查询球队列表
示例:使用不同列作为查询条件
(1)TeamMapper接口添加如下内容:
List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);
(2)TeamMapper.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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<select id="queryByFiled" resultType="com.lxy.pojo.Team">
select * from team where ${column}=#{columnValue}
</select>
</mapper>
(3)添加测试方法
package com.lxy.test;
import com.lxy.mapper.TeamMapper;
import com.lxy.pojo.QueryVO;
import com.lxy.pojo.Team;
import com.lxy.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TeamMapperTest {
private SqlSession sqlSession = MybatisUtil.getSqlSession();
@Test
public void testQueryByFiled(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
System.out.println("根据球队名称查询:");
List<Team> teams = mapper.queryByFiled("teamName","LALALA");
teams.forEach(team -> System.out.println(team));
System.out.println("根据球队位置查询:");
List<Team> teams2 = mapper.queryByFiled("location","北京");
teams2.forEach(team -> System.out.println(team));
}
}
测试结果:
8.5 输出映射
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。如果返回的是集合,设置的是集合元素的类型,而不是集合本身。resultType 和 resultMap,
不能同时使用。
8.5.1 输出简单类型
案例:
(1)TeamMapper接口添加如下内容:
int getCount();
(2)TeamMapper.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="名称必须与映射的接口的名字一致,是完全限定名"-->
<mapper namespace="com.lxy.mapper.TeamMapper">
<!-- 只有返回的结果是单行的时候,返回值类型才可以指定为基本类型
如果是单行多列,也取不到后面的列的值;
如果返回多行会报异常:TooManyResultsException-->
<select id="getCount" resultType="java.lang.Integer">
select count(teamId) from team
</select>
</mapper>
(3)测试类添加方法:
@Test
public void testCount(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
int count = mapper.getCount();
System.out.println("总共的行数:"+count);
}
8.5.2 输出pojo类型
案例:参考之前的查询所有球队信息
List<Team> queryAll();
<!--接口方法返回是集合类型,但是映射文件中的resultType需要指定集合中的类型,不是集合本身。-->
<select id="queryAll" resultType="com.lxy.pojo.Team">
select * from team;
</select>
8.5.3 输出Map类型
当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map<Object,Object>.
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。
如果有多行,使用List<Map<Object,Object>>.
案例:根据id查询球队名称和位置。
(1)TeamMapper接口添加如下内容:
Map<Object,Object> queryTwoColumn(int teamId);
List<Map<Object,Object>> queryTwoColumnList();
(2)TeamMapper.xml配置文件中添加如下:
<select id="queryTwoColumn" resultType="java.util.HashMap">
select teamName,location from team where teamId=#{id}
</select>
<select id="queryTwoColumnList" resultType="java.util.HashMap">
select teamName,location from team
</select>
(3)添加测试类
@Test
public void test08(){
Map<String, Object> map = teamMapper.queryTwoColumn();
System.out.println(map);
}
@Test
public void test09(){
List<Map<String, Object>> list = teamMapper.queryTwoColumnList();
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
测试结果:
8.5.4 resultMap
resultType要求数据库中的列名和实体类中的保持一致
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系。更灵活的把列值赋值给指定属性。常用在列名和 java 对象属性名不一样的情况。
(1)在TeamMapper接口中添加方法
List<Team> queryAll2();
(2)添加select节点,并定义 resultMap,指定列名和属性的对应关系
<!--resultMap 和resultType不能同时出现
resultMap:是引用的自己创建resultMap的id-->
<select id="queryAll2" resultMap="baseResultMap">
select * from team;
</select>
<!--创建resultMap:相当于自己编写表中的列与实体类中的属性的映射
id:resultMap的名称,要求唯一
type:期待要映射为java的类型
-->
<resultMap id="baseResultMap" type="com.lxy.pojo.Team">
<!--一般主键列用id,其余列用result
column:表示数据库表中的列名,不区分大小写
property:表示实体类中的对应的属性名,区分大小写
javaType:实体类中的对应的属性的类型,可以省略,mybatis会自己推断
jdbcType="数据库中的类型column的类型" 一般省略
-->
<id column="teamId" property="teamId" javaType="java.lang.Integer" ></id>
<result column="teamName" property="teamName" javaType="java.lang.String"></result>
<result column="location" property="location" javaType="java.lang.String"></result>
<result column="createTime" property="createTime" javaType="java.util.Date"></result>
</resultMap>
(3)添加测试方法
@Test
public void test10(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
List<Team> teams = mapper.queryAll2();
teams.forEach(team-> System.out.println(team));
}
测试结果:
8.6 数据库表中列与实体类属性不一致的处理方式
案例准备 创建表:
use mybatis;
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`user_id` int NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户姓名',
`user_age` int NULL DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` VALUES (1, '贾宝玉', 14);
INSERT INTO `users` VALUES (2, '林黛玉', 13);
INSERT INTO `users` VALUES (3, '薛宝钗', 15);
SET FOREIGN_KEY_CHECKS = 1;
8.6.1 使用列别名
(1)创建实体类
package com.kkb.pojo;
public class Users {
private Integer userId;
private String userName;
private Integer userAge;
@Override
public String toString() {
return "Users{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", userAge=" + userAge +
'}';
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getUserAge() {
return userAge;
}
public void setUserAge(Integer userAge) {
this.userAge = userAge;
}
}
(2)创建UsersMapper接口
public interface UsersMapper {
Users queryByID(int userId);
}
(3)映射文件UsersMapper.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.lxy.mapper.UsersMapper">
<!--方式1:resultType中的实体类的属性作为查询语句中的别名,让别名和属性保持一致-->
<select id="queryByID" resultType="com.lxy.pojo.Users">
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
</select>
</mapper>
(4)编写测试类
package com.lxy.test;
import com.lxy.mapper.UsersMapper;
import com.lxy.pojo.Users;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
public class TestUsersMapper {
private UsersMapper usersMapper = MybatisUtil.getSqlSession().getMapper(UsersMapper.class);
@Test
public void test01(){
Users users = usersMapper.queryByID(1);
System.out.println(users);
}
}
测试结果:
8.6.2 使用resultMap
(1)接口UsersMapper.java添加方法
Users queryByID2(int userId);
(2)映射文件UsersMapper.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.lxy.mapper.UsersMapper">
<!--方式1:resultType中的实体类的属性作为查询语句中的别名,让别名和属性保持一致-->
<select id="queryByID" resultType="com.lxy.pojo.Users">
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
</select>
<!--方式2:通过resultMap自行映射-->
<select id="queryByID2" resultMap="baseMap">
select * from users where user_id=#{id};
</select>
<resultMap id="baseMap" type="com.lxy.pojo.Users">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_age" property="userAge"/>
</resultMap>
</mapper>
(3)测试类
package com.lxy.test;
import com.lxy.mapper.UsersMapper;
import com.lxy.pojo.Users;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
public class TestUsersMapper {
private UsersMapper usersMapper = MybatisUtil.getSqlSession().getMapper(UsersMapper.class);
@Test
public void test01(){
Users users = usersMapper.queryByID(1);
System.out.println(users);
}
@Test
public void test02(){
Users user = usersMapper.queryByID2(1);
System.out.println(user);
}
}
测试结果:
九、Mybatis的全局配置文件
案例中使用的 mybatis.xml就是Mybatis的全局配置文件。
全局配置文件需要在头部使用约束文件
<?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">
9.1 配置的内容
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactory-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
Mappers---映射器:注册映射文件用
9.2 属性(properties)
属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource节点中的properties节点),也可以在 Java 属性文件中配置这些属性。
数据源中有连接数据库的四个参数数据,我们一般都是放在专门的属性文件中,mybatis的全局配置文件直接从属性文件中读取数据即可。
(1)在 resources 目录创建 jdbc.properties 文件,文件名称可以自定义。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.a=???
(2)mybatis的全局配置文件引入属性文件
<properties resource="jdbc.properties"/>
(3)使用属性文件中的值
<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>
9.3 设置 settings
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为.例如我们配置的日志就是应用之一。其余内容参考mybatis – MyBatis 3 | 配置
<!--配置日志-->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
9.4 类型别名 typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
9.4.1 Mybatis中已经支持的别名
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
9.4.2 自定义别名
<!--自定义类型别名-->
<typeAliases>
<!--对单个的实体类定义别名-->
<typeAlias type="com.lxy.pojo.Team" alias="Team"/>
<!--推荐写法:批量定义别名:扫描指定包下的所有类,同时别名定义为类名,别名的首字母大小写都可以-->
<package name="com.lxy.pojo"/>
</typeAliases>
9.5 映射器 Mappers
配置有多种方式:
9.5.1 使用相对于类路径的资源引用
语法:<mapper resource=""/>
使用相对于类路径的资源,从 classpath 路径查找文件
例如:<mapper resource="com/lxy/mapper/TeamMapper.xml" />
9.5.2 使用映射器接口实现类的完全限定类名
语法:<mapper class=""/>
使用的mapper接口的完全限定名
要求:接口和映射文件同包同名
例如<mapper class="com.lxy.mapper.GameRecordMapper"/>
9.5.3 将包内的映射器接口实现全部注册为映射器--推荐
语法:<package name=""/>
指定包下的所有Mapper接口
如:<package name="com.lxy.mapper"/>
注意:此种方法要求 Mapper接口名称和 mapper 映射文件名称相同,且在同一个目录中。
9.6 dataSource标签
Mybatis 中访问数据库支持连接池技术,而且是采用的自己的连接池技术。在 Mybatis 的 mybatis.xml配置文件中,通过来实现 Mybatis 中连接池的配置。MyBatis 在初始化时,根据的 type 属性来创建相应类型的的数据源 DataSource。
Mybatis 的数据源分为三类:
UNPOOLED: 不使用连接池的数据源
POOLED:使用连接池的数据源
JNDI:使用JNDI实现的数据源
前两个数据源都实现javax.sql.DataSource接口
9.7 事务
9.7.1 默认是需要手动提交事务的
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 Connection对象的 commit(), rollback() .Connection 对象的 setAutoCommit()方法来设置事务提交方式的。自动提交和手工提交、
<transactionManager type="JDBC"/>
该标签用于指定 MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
(1)JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
(2)MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
9.7.2 自动提交事务
SqlSessionFactory的openSession方法由重载,可以设置自动提交的方式。
如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
十、Mybatis中的关系映射
表结构如图:
10.1对一关系的映射
10.1.1 添加实体类
package com.lxy.pojo;
public class Player {
private Integer playerId;
private String playerName;
private Integer playerNum;
private Integer teamId;
//多对一的体现:多方持有一方的对象 要有get方法
private Team team1;//关联对象--多个球员可以属于同一个球队;
private Team team2;//关联对象--多个球员可以属于同一个球队;
private Team team3;//关联对象--多个球员可以属于同一个球队;
public Integer getPlayerId() {
return playerId;
}
public void setPlayerId(Integer playerId) {
this.playerId = playerId;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
public Integer getPlayerNum() {
return playerNum;
}
public void setPlayerNum(Integer playerNum) {
this.playerNum = playerNum;
}
public Integer getTeamId() {
return teamId;
}
public void setTeamId(Integer teamId) {
this.teamId = teamId;
}
public Team getTeam1() {
return team1;
}
public void setTeam1(Team team1) {
this.team1 = team1;
}
public Team getTeam2() {
return team2;
}
public void setTeam2(Team team2) {
this.team2 = team2;
}
public Team getTeam3() {
return team3;
}
public void setTeam3(Team team3) {
this.team3 = team3;
}
@Override
public String toString() {
return "Player{" +
"playerId=" + playerId +
", playerName='" + playerName + '\'' +
", playerNum=" + playerNum +
", teamId=" + teamId +
", team1=" + team1 +
", team2=" + team2 +
", team3=" + team3 +
'}';
}
}
10.1.2 添加对应的mapper接口
package com.lxy.mapper;
import com.lxy.pojo.Player;
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
}
10.1.3 对一映射方式1:通过关联对象打点调用属性的方式
要求:两表的连接查询
<?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.lxy.mapper.PlayerMapper">
<resultMap id="baseResultMap" type="com.lxy.pojo.Player">
<id column="playerId" property="playerId"></id>
<result column="playerName" property="playerName"></result>
<result column="playerNum" property="playerNum"></result>
<result column="teamId" property="teamId"></result>
</resultMap>
<select id="queryById1" resultMap="joinTeamResult1">
select * from player p inner join team t
on p.teamId=t.teamId where playerId=#{id}
</select>
<resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
<result column="teamId" property="team1.teamId"></result>
<result column="location" property="team1.location"></result>
<result column="teamName" property="team1.teamName"></result>
<result column="createTime" property="team1.createTime"></result>
</resultMap>
</mapper>
编写测试类:
package com.lxy.test;
import com.lxy.mapper.PlayerMapper;
import com.lxy.pojo.Player;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
public class TestPlayerMapper {
PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
@Test
public void test01(){
Player player = playerMapper.queryById1(1);
System.out.println(player);
}
}
测试结果:
10.1.4 对一映射方式2:直接引用关联对象的Mapper映射
要求:1、两表的连接查询
2、关联对象中已经存在被引用的resultMap
<select id="queryById2" resultMap="joinTeamResult2">
select * from player p inner join team t
on p.teamId=t.teamId where playerId=#{id}
</select>
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<association property="team2" javaType="Team"
resultMap="com.lxy.mapper.TeamMapper.baseResultMap"/>
</resultMap>
测试类:
package com.lxy.test;
import com.lxy.mapper.PlayerMapper;
import com.lxy.pojo.Player;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
public class TestPlayerMapper {
PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
@Test
public void test01(){
Player player = playerMapper.queryById1(1);
System.out.println(player);
}
@Test
public void test02(){
Player player = playerMapper.queryById2(1);
System.out.println(player);
}
}
测试结果:
10.1.5 对一映射方式3:直接引用关联对象的单独查询的方法
要求:1、不需要两表的连接查询
2、关联对象中已经存在被引用的查询方法
<select id="queryById3" resultMap="joinTeamResult3">
select * from player where playerId=#{id}
</select>
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<association property="team3" javaType="Team"
select="com.lxy.mapper.TeamMapper.queryById" column="teamId"/>
</resultMap>
测试类:
package com.lxy.test;
import com.lxy.mapper.PlayerMapper;
import com.lxy.pojo.Player;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
public class TestPlayerMapper {
PlayerMapper playerMapper= MybatisUtil.getSqlSession().getMapper(PlayerMapper.class);
@Test
public void test01(){
Player player = playerMapper.queryById1(1);
System.out.println(player);
}
@Test
public void test02(){
Player player = playerMapper.queryById2(1);
System.out.println(player);
}
@Test
public void test03(){
Player player = playerMapper.queryById3(1);
System.out.println(player);
}
}
测试结果:
10.2 对多关系的映射
修改实体类Team.java:
public class Team {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
//一对多的体现:一方持有多方的对象
private List<Player> playerList1;//关联对象--一个球队可以拥有多个球员
private List<Player> playerList2;//关联对象--一个球队可以拥有多个球员
TeamMapper.java接口添加方法:
public interface TeamMapper {
Team queryById1(int teamId);
Team queryById2(int teamId);
PlayerMapper.java接口中添加方法:
public interface PlayerMapper {
List<Player> queryByTeamId(int teamId);
10.2.1 方式1:连接查询+引用关联对象的结果映射
TeamMapper.xml添加:
<select id="queryById1" resultMap="joinResult1">
select * from team t join player p
on t.teamId=p.teamId where t.teamId=#{id};
</select>
<!--方式1:
对多的连接查询:对多使用collection
property="关联对象的集合名称"
javaType="关联对象的集合类型"
ofType="关联对象的集合的泛型"
resultMap="引用关联对象的结果映射"
-->
<resultMap id="joinResult1" type="Team" extends="baseResultMap">
<collection property="playerList1" javaType="java.util.ArrayList" ofType="player"
resultMap="com.lxy.mapper.PlayerMapper.baseResultMap"/>
</resultMap>
添加测试类:
@Test
public void test11(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Team team = mapper.queryById1(1001);
System.out.println(team);
}
测试结果:
10.2.2 方式2:引用关联对象的单独查询的方法
TeamMapper.xml添加:
<select id="queryById2" resultMap="joinResult2">
select * from team where teamId=#{id};
</select>
<!--方式2:
对多的连接查询:对多使用collection
property="关联对象的集合名称"
javaType="关联对象的集合类型"
ofType="关联对象的集合的泛型"
select="引用关联对象的单独查询的方法":使用的前提是关联对象中该方法可用
column="引用关联对象的单独查询的方法的参数,一般是外键"
-->
<resultMap id="joinResult2" type="Team" extends="baseResultMap">
<collection property="playerList2" javaType="java.util.ArrayList" ofType="player"
select="com.lxy.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
</resultMap>
PlayerMapper.xml添加如下内容:
<select id="queryByTeamId" resultType="Player">
select * from player where teamId=#{id}
</select>
测试类:
@Test
public void test12(){
TeamMapper mapper = sqlSession.getMapper(TeamMapper.class);
Team team = mapper.queryById2(1001);
System.out.println(team);
}
测试结果:
十一、动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
之前学习过 JSTL,所以动态 SQL 元素会让你感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少
11.1 where标签在select中的使用
(1)原先的多条件查询做法
/* 原有的多条件分析:都是通过java中的字符串拼接实现
String sql="select * from team where 1 = 1 ";
// 如果用户输入了名称,就模糊查询
and teamName like '%?%'
// 如果用户输入了日期,按照日期区间查询
and createTime> ? and createTime< ?
//如果输入了地区,按照地区查询
and location =?";*/
if(vo.getName()!=null && !"".equals(vo.getName().trim())){
sql+=" and teamName like '%"+vo.getName().trim()+"%'";
}
if(vo.getBeginTime()!=null ){
sql+=" and getEndTime>"+vo.getBeginTime();
}
if(vo.getBeginTime()!=null ){
sql+=" and createTime<="+vo.getEndTime();
}
if(vo.getLocation()!=null && !"".equals(vo.getLocation().trim())){
sql+=" and location ="+vo.getLocation().trim();
}
(2)自己封装的查询条件类QueryTeamVO.java
package com.lxy.pojo;
import java.util.Date;
public class QueryTeamVO {
private String name;
private Date beginTime ;
private Date endTime;
private String location;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBeginTime() {
return beginTime;
}
public void setBeginTime(Date beginTime) {
this.beginTime = beginTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
(3)TeamMapper.java接口添加:
List<Team> queryByVO(QueryTeamVO vo);
(4)TeamMapper.xml映射文件添加:
<!--多条件查询:
模糊查询的写法可以使用3种方式:
方式1: and teamName like #{name} ,传递参数的时候带上%,例如vo.setName("%人%")
方式2: and teamName like ‘%${name}%’ 传递参数的时候没有%,例如vo.setName("人")
方式3: and teamName like concat(concat('%',#{name}),'%') 例如vo.setName("人")
concat(str1,str2)函数是字符串拼接使用-->
<select id="queryByVO" parameterType="QueryVO" resultMap="baseResultMap">
select * from team
<where>
<!-- 如果用户输入了名称,就模糊查询 and teamName like '%?%'-->
<if test="name!=null ">
and teamName like concat(concat('%',#{name}),'%')
</if>
<if test="beginTime!=null ">
and createTime>=#{beginTime}
</if>
<if test="endTime!=null ">
and createTime<=#{endTime}
</if>
<if test="location!=null ">
and location=#{location}
</if>
</where>
</select>
(5)测试方法
package com.lxy.test;
import com.lxy.mapper.TeamMapper;
import com.lxy.pojo.QueryTeamVO;
import com.lxy.pojo.Team;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
import java.util.Date;
import java.util.List;
public class TestSql {
private TeamMapper mapper = MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test01(){
QueryTeamVO vo = new QueryTeamVO();
vo.setName("球");
vo.setEndTime(new Date());
vo.setLocation("北京");
List<Team> teams = mapper.queryByVO(vo);
teams.forEach(team -> System.out.println(team));
}
}
测试结果:
11.2 set标签在update中的使用
之前的做法如果没给其余属性赋值,那么null值会直接覆盖之前属性的值,所以并不合理
11.2.1 更新的原有写法
TeamMapper.java接口中的方法:
int update(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location},createTime=#{createTime}
where teamId=#{teamId}
</update>
测试类中添加测试方法:
@Test
public void test2(){
Team team=new Team();
team.setTeamId(1055);
team.setTeamName("lina");
int update = teamMapper.update(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
测试结果:
11.2.2 使用set标签构建动态的SQL语句
TeamMapper.java接口中添加方法:
int update1(Team team);
TeamMapper.xml映射文件对应的内容:
<update id="update1" parameterType="com.lxy.pojo.Team">
update team
<set>
<if test="teamName!=null">
teamName=#{teamName}
</if>
<if test="location!=null">
location=#{location},
</if>
<if test="createTime!=null">
createTime=#{createTime},
</if>
where teamId=#{teamId}
</set>
</update>
测试类:
@Test
public void test02(){
Team team=new Team();
team.setTeamId(1020);
team.setTeamName("lina");
int update = mapper.update1(team);
MybatisUtil.getSqlSession().commit();
System.out.println(update);
}
测试结果:
11.3 forEach标签
11.3.1 批量添加
TeamMapper.java接口中添加方法:
void addList(List<Team> list);
TeamMapper.xml映射文件对应的内容:
<!--批量添加-->
<insert id="addList" parameterType="arrayList">
insert into team (teamName,location) values
<!--collection:要遍历的集合:参数是集合类型,直接写list
item:遍历的集合中的每一个数据
separator:将遍历的结果用 , 分割开-->
<foreach collection="list" item="t" separator=",">
(#{t.teamName},#{t.location})
</foreach>
</insert>
编写测试类:
@Test
public void test03(){
List<Team> list=new ArrayList<>();
for(int i=1;i<=3;i++){
Team team=new Team();
team.setTeamName("lina"+i);
team.setLocation("bj"+i);
list.add(team);
}
mapper.addList(list);
MybatisUtil.getSqlSession().commit();
}
测试结果:
11.3.2 批量删除
TeamMapper.java接口中添加方法:
void delList(List<Integer> list);
TeamMapper.xml映射文件对应的内容:
<delete id="delList" >
delete from team where teamId in
<!--collection:要遍历的集合;参数是集合类型,直接写list
item:遍历的集合中的每一个数据
separator:将遍历的结果用,分割
open="(" close=")":表示将遍历结果用open close包裹起来-->
<foreach collection="list" item="teamId" separator="," open="(" close=")">
#{teamId}
</foreach>
</delete>
编写测试类:
@Test
public void test04(){
List<Integer> list = new ArrayList<>();
list.add(1021);
list.add(1022);
list.add(1023);
mapper.delList(list);
MybatisUtil.getSqlSession().commit();
}
测试结果:
十二、分页插件
12.1 jar依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
12.2 在Mybatis全局配置文件中添加插件配置
<!-- 引入 pageHelper插件 -->
<!--注意这里要写成PageInterceptor, 5.0之前的版本都是写PageHelper, 5.0之后要换成PageInterceptor-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--reasonable:分页合理化参数,默认值为false,直接根据参数进行查询。
当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数 时),会查询最后一页。
方言可以省略,会根据连接数据的参数url自动推断-->
<!--<property name="reasonable" value="true"/>-->
</plugin>
</plugins>
12.3 使用插件
@Test
public void test5() {
// PageHelper.startPage 必须紧邻查询语句,而且只对第一条查询语句生效
PageHelper.startPage(2,5);
List<Team> teams = mapper.queryAll();//查询语句结尾不能有分号
teams.forEach(team-> System.out.println(team));
PageInfo<Team> info=new PageInfo<>(teams);
System.out.println("分页信息如下:");
System.out.println("当前页:"+info.getPageNum());
System.out.println("总页数:"+info.getPages());
System.out.println("前一页:"+info.getPrePage());
System.out.println("后一页:"+info.getNextPage());
System.out.println("navigatepageNums:"+info.getNavigatepageNums());
for (int num : info.getNavigatepageNums()) {
System.out.println(num);
}
}
测试结果:
PageInfo.java的部分源码:
package com.github.pagehelper;
import java.util.Collection;
import java.util.List;
/**
* 对Page<E>结果进行包装
* <p/>
* 新增分页的多项属性,主要参考:http://bbs.csdn.net/topics/360010907
*
* @author liuzh/abel533/isea533
* @version 3.3.0
* @since 3.2.2
* 项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class PageInfo<T> extends PageSerializable<T> {
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总页数
private int pages;
//前一页
private int prePage;
//下一页
private int nextPage;
//是否为第一页
private boolean isFirstPage = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;
十三、Mybatis缓存
13.1 缓存作用
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。
MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis的缓存结构体系:
13.2 一级缓存:自动开启,SqlSession级别的缓存
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
13.2.1 一级缓存分析
工作原理图:
当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存中;
当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味着不需要再去向数据库发送查询语句。
如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读。前提:SqlSession未关闭。
测试:
@Test
public void test13(){
Team t1=sqlSession.selectOne("com.lxy.mapper.TeamMapper.queryById",1001);//第一次查询,先查缓存,此时缓存中没有,继续向数据库发送查询语句
System.out.println(t1);//查询完毕之后数据被自动存入缓存区域
Team t2=sqlSession.selectOne("com.lxy.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存中已经有了该数据,可以直接获取,不需要发送查询语句
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
sqlSession=MybatisUtil.getSqlSession();//再次获取连接,此时缓存为空
Team t3=sqlSession.selectOne("com.lxy.mapper.TeamMapper.queryById",1001);//新连接下第一次查询,肯定发送查询语句
System.out.println(t3);//查询完毕之后数据被自动存入缓存区域
int num=sqlSession.delete("com.lxy.mapper.TeamMapper.del",10000);
sqlSession.commit();//提交之后缓存被整个清空
System.out.println("删除结果:"+num);
Team t4=sqlSession.selectOne("com.lxy.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存已经被上一次的提交清空了,所以还是需要发送查询语句
System.out.println(t4);
sqlSession.close();
}
测试结果:
13.2.2 清空缓存的方式
1、 session.clearCache( ) ;
2、 execute update(增删改) ;
3、 session.close( );
4、 xml配置 flushCache="true" ;
5、 rollback;
6、 commit。
13.3 二级缓存:Mapper级别的缓存
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
二级缓存原理图:
13.3.1 使用二级缓存步骤
二级缓存是mapper范围级别的,默认不启用。
1、在Mybatis框架的全局配置文件中开启二级缓存
<!--是否开启二级缓存,默认false-不开启, true:开启-->
<setting name="cacheEnabled" value="true"/>
2、在需要二级缓存的Mapper中添加缓存标志
3、实体类必须实现Serializable接口
4、测试二级缓存
如果两个session不是从同一个Factory获取,那么二级缓存将不起作用。
@Test
public void test2() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
Team t1 = sqlSession1.selectOne("com.lxy.mapper.TeamMapper.queryById", 1001);//先查缓存,没有,先数据库,查询完毕写入二级缓存
System.out.println(t1);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
Team t2 = sqlSession2.selectOne("com.lxy.mapper.TeamMapper.queryById", 1001);//先查缓存,有,直接获取,不需要查询数据库
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession3 = MybatisUtil.getSqlSession();
int num = sqlSession3.delete("com.lxy.mapper.TeamMapper.del", 10000);//删除成功
System.out.println("删除的结果:" + num);
sqlSession3.commit();//提交之后清空二级缓存
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
SqlSession sqlSession4 = MybatisUtil.getSqlSession();
Team t3 = sqlSession4.selectOne("com.lxy.mapper.TeamMapper.queryById", 1001);先查缓存,曾经有,但是上一个提交已经清空了缓存,所以只能去数据库中查询,查询完毕写入二级缓存
System.out.println(t3);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
}
13.3.2 二级缓存的禁用
对于变化比较频繁的SQL,可以禁用二级缓存。
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存,意味着该SQL语句每次只需都去查询数据库,不会查询缓存。
useCache默认值是true。对于一些很重要的数据尽不放在二级缓存中。
13.3.3 缓存的属性配置
<cache>
<property name="eviction" value="LRU"/><!--回收策略为LRU-->
<property name="flushInterval" value="60000"/><!--自动刷新时间间隔为60S-->
<property name="size" value="1024"/><!--最多缓存1024个引用对象-->
<property name="readOnly" value="true"/><!--只读-->
</cache>
源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CacheNamespace {
Class<? extends Cache> implementation() default PerpetualCache.class;
Class<? extends Cache> eviction() default LruCache.class;
long flushInterval() default 0;
int size() default 1024;
boolean readWrite() default true;
boolean blocking() default false;
Property[] properties() default {};
}
/**属性介绍:
1.映射语句文件中的所有select语句将会被缓存;
2.映射语句文件中的所有CUD操作将会刷新缓存;
3.缓存会默认使用LRU(Least Recently Used)算法来收回;
3.1、LRU – 最近最少使用的:移除最长时间不被使用的对象。
3.2、FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.3、SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
3.4、WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
4.缓存会根据指定的时间间隔来刷新(默认情况下没有刷新间隔,缓存仅仅调用语句时刷新);
5.缓存会存储列表集合或对象(无论查询方法返回什么),默认存储1024个对象。
6.缓存会被视为是read/write(可读/可写)的缓存,意味着检索对象不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
**/
如果想在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="com.lxy.mapper.TeamMapper" />
//引用TeamMapper命名空间中的cache。
十四、反向生成插件
14.1 插件的配置
在pom.xml文件中的中中添加如下插件配置:
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<!--配置文件的路径-->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
</plugin>
generatorConfig.xml内容:
其内容需要自己改一部分
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<!--1、数据库驱动jar:添加自己的jar路径 -->
<classPathEntry
location="D:\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar" />
<context id="MyBatis" targetRuntime="MyBatis3">
<!--去除注释 -->
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--2、数据库连接 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT"
userId="root"
a="???">
</jdbcConnection>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer;
为 true时把JDBC DECIMAL和NUMERIC类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--3、生成实体类 指定包名 以及生成的地址 (可以自定义地址,但是路径不存在不会自动创建
使用Maven生成在target目录下,会自动创建) -->
<javaModelGenerator targetPackage="org.xzk.pojo"
targetProject="src\main\java">
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--4、生成SQLmapper.xml映射文件 -->
<sqlMapGenerator targetPackage="org.xzk.mapper"
targetProject="src\main\resources">
</sqlMapGenerator>
<!--5、生成Dao(Mapper)接口文件,-->
<javaClientGenerator type="XMLMAPPER"
targetPackage="org.xzk.mapper"
targetProject="src\main\java">
</javaClientGenerator>
<!--6、要生成哪些表(更改tableName和domainObjectName就可以) -->
<!-- tableName:要生成的表名
enableCountByExample:Count语句中加入where条件查询,默认为true开启
enableUpdateByExample:Update语句中加入where条件查询,默认为true开启
enableDeleteByExample:Delete语句中加入where条件查询,默认为true开启
enableSelectByExample:Select多条语句中加入where条件查询,默认为true开启
selectByExampleQueryId:Select单个对象语句中加入where条件查询,默认为true开启
-->
<table tableName="team">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="player">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="gameRecord">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
注意只能运行一次,运行完毕显示BUILD SUCCESS即为成功。
如果想重新生成,需要将之前生成的删除掉
14.2 反向生成插件的使用
1.使用时需要注意,生成后所使用的mapper需要在mybatis.xml中注册
2.使用时还需注意,如果其他数据库的表名和你需要生成的表名相同,那么你有可能会生成两个baseResultMap,使用时一点会报错
package com.lxy.test;
import com.lxy.util.MybatisUtil;
import org.junit.Test;
import org.xzk.mapper.TeamMapper;
import org.xzk.pojo.Team;
import org.xzk.pojo.TeamExample;
import java.util.List;
/**
* 测试反向生成的内容
*/
public class TestGenerator {
private TeamMapper mapper = MybatisUtil.getSqlSession().getMapper(TeamMapper.class);
@Test
public void test1(){
Team team = mapper.selectByPrimaryKey(1001);
System.out.println(team);
}
@Test
public void test2(){
Team team = new Team();
team.setTeamName("LALALA");
team.setLocation("bjjj");
int i = mapper.insertSelective(team);
MybatisUtil.getSqlSession().commit();
System.out.println(i);
}
@Test
public void test3(){
//理解为为多条件、排序等服务的类
TeamExample example = new TeamExample();
//理解为盛放条件的容器
TeamExample.Criteria criteria = example.createCriteria();
//向容器中添加条件
criteria.andTeamNameLike("%队%");
//criteria.andTeamIdBetween(1001,1020);
example.setOrderByClause("teamId asc");
List<Team> teams = mapper.selectByExample(example);
for (Team team : teams) {
System.out.println(team);
}
}
}
如果你读到了这里,那请给博主点个攒吧!如果你有什么问题,可以在评论区留言,我会及时回答你。