目录
九,获取自增的id值
有时候新插入一条记录后,需要获取这条记录的id(id往往设置为自增),这时就需要对mapper.xml文件进行一些修改。
1,id为Integer类型
1,修改TeamMapper.xml文件中的add
select LAST_INSERT_ID()就是用来获取最后插入数据的id
<!--添加一个球队
parameterType="com.kkb.pojo.Team" 将对象作为参数,
#{值} 值必须是实体类中的属性名称,其实就是占位符?
-->
<insert id="add" parameterType="com.xxy.pojo.Team" >
<!--新增成功之后将自增的ID赋值给参数属性teamId
keyProperty:表示新增的id值赋值到哪个属性值红
order:AFTER/BEFORE两个取值,表示selectKey中的sql语句在insert语句之前还是之后执行
resultType:表示返回值类型
-->
<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>
2,测试
@Test
public void testTeamMapper() {
TeamMapper teamMapper = sqlSession.getMapper(TeamMapper.class);
Team team = new Team();
team.setTeamName("云堇");
team.setLocation("璃月");
team.setCreateTime(new Date());
int num = teamMapper.add(team);
sqlSession.commit();// 提交之后,就已经执行了mapper.xml中selectKey的语句,即将id赋值给当前的team对象
System.out.println(num);
System.out.println("新增记录id: " + team.getTeamId());
}
2,id为String类型
1,设计主键为字符串的表
2,创建新的实体类、mapper接口
3,创建新的mapper.xml文件
插入数据之前先获取36位字符串作为id放入属性recordId中
<!--namespace="名称必须与映射的类的名字一致,是完全限定名"-->
<mapper namespace="com.kkb.mapper.GameRecordMapper">
<!--添加一条比赛记录 -->
<insert id="add" parameterType="com.kkb.pojo.GameRecord" >
<!--插入数据之前先获取36位字符串作为id放入属性recordId中
order="AFTER/BEFORE" 在insert执行之前还是之后
resultType="返回值的类型"
-->
<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>
4,在mybatis.xml中添加新的mapper.xml配置文件路径
5,测试
十,输入映射——传递多个参数
1,parameterType
parameterType:接口中方法参数的类型,类型必须是完全限定名或别名(稍后讲别名)。该属性非必须,因为Mybatis框架能自行判断具体传入语句的参数,默认值为未设置(unset)。
当sql语句中需要多个参数时,就不能使用parameterType方法了。
2,通过参数下标索引传递参数
#{arg[0]}、#{arg[1]}......、
#{param[1]}、#{param[2]}、
1,首先在TeamMapper接口中添加方法声明
List<Team> queryByRange(int min, int max);
2,在TeamMapper.xml中添加节点
<mapper namespace="com.xxy.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.xxy.pojo.Team">
select * from team where teamId >=#{param1} and teamId <=#{param2};
</select>
3,编写测试方法
@Test
public void testTeamMapper() {
TeamMapper teamMapper = sqlSession.getMapper(TeamMapper.class);
List<Team> teams = teamMapper.queryByRange(1000, 1003);
for (Team t : teams) System.out.println(t);
}
3,通过@Param注解传递多个参数(常用!)
1,修改TeamMapper接口中的方法声明
List<Team> queryByRange(@Param("min") int min, @Param("max") int max);
2,修改TeamMapper.xml中节点参数
<mapper namespace="com.xxy.mapper.TeamMapper">
<!--方式2:通过注解的方式:
#{}中的名称必须与接口的方法中的参数注解@Param()保持一致
select * from team where teamId >=#{param1} and teamId <= #{param2};
不推荐,但是语法也是正确的,但是不能使用arg0,arg1......
-->
<select id="queryByRange" resultType="com.xxy.pojo.Team">
select * from team where teamId >=#{min} and teamId <=#{max};
</select>
3,测试方法同上
4,通过map传递多个参数
1,修改TeamMapper接口中的方法声明
List<Team> queryByRange(Map<String, Integer> map);
2,修改TeamMapper.xml中节点参数
<mapper namespace="com.xxy.mapper.TeamMapper">
<!--方式3:通过map来传递多个参数:映射文件中的参数占位符必须和map中的String类型的字段名称一样-->
<select id="queryByRange" resultType="com.xxy.pojo.Team">
select * from team where teamId >=#{min} and teamId <=#{max};
</select>
3,修改测试方法
@Test
public void testTeamMapper() {
TeamMapper teamMapper = sqlSession.getMapper(TeamMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("min", 1000);
map.put("max", 1003);
List<Team> teams = teamMapper.queryByRange(map);
for (Team t : teams) System.out.println(t);
}
5,通过pojo类传递多个参数
与map传递多个参数类似,要求映射文件中的参数占位符必须和pojo类中的属性完全一致。适用于多个参数类型不一致的场景。
1,实体类
public class QueryVO {
private String name;
private Integer min;
private Integer max;
private String location;
}
2,TeamMapper接口添加如下内容
List<Team> queryByCondition(QueryVO vo);
3,TeamMapper.xml配置文件中添加如下
方式4:#{}中的名称必须pojo的参数的属性保持一致-->
<select id="queryByCondition" resultType="com.xxy.pojo.Team">
select * from team
where teamId>=#{min} and teamId<=#{max}
and teamName like #{name} and location=#{location}
</select>
4,测试方法
@Test
public void test04(){
QueryVO vo=new QueryVO();
vo.setLocation("洛杉矶");
vo.setName("%球队%");
vo.setMin(1001);
vo.setMax(1111);
List<Team> teams = teamMapper.queryByCondition(vo);
teams.forEach(team -> System.out.println(team));
}
十一,#{}和${}的区别
1,#{}
表示一个占位符,通知Mybatis 使用实际的参数值代替。并使用 PrepareStatement 对象执行 sql 语句, #{…}代替sql 语句的“?”。这个是Mybatis 中的首选做法,安全迅速。
<select id="queryById" parameterType="int" resultType="com.xxy.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}
-->
2,${}
表示字符串原样替换(SQL注入漏洞的通常做法),通知Mybatis 使用$包含的“字符串”替换所在位置。使用 Statement或者PreparedStatement 把 sql 语句和${}的内容连接起来。
一般用在替换表名,列名,不同列排序等操作。
例如:根据球队名称,球队位置查询球队列表
2.1 举例——使用#{}方法
1,TeamMapper接口添加如下内容:
List<Team> queryByName(String teamName);
List<Team> queryByLocation(String location);
2,TeamMapper.xml配置文件中添加如下
<select id="queryByName" resultType="com.xxy.pojo.Team">
select * from team where teamName=#{teamName}
</select>
<select id="queryByLocation" resultType="com.xxy.pojo.Team">
select * from team where location=#{location}
</select>
3,测试方法
@Test
public void test05(){
System.out.println("根据名称查询:");
List<Team> teams = teamMapper.queryByName("胡桃");
teams.forEach(team -> System.out.println(team));
System.out.println("根据位置查询:");
List<Team> teams2 = teamMapper.queryByLocation("璃月");
teams2.forEach(team -> System.out.println(team));
}
2.2 举例——使用${}方法
1,TeamMapper接口添加如下内容:
List<Team> queryByFiled(@Param("column") String column,@Param("columnValue") String columnValue);
2,TeamMapper.xml配置文件中添加如下:
<select id="queryByFiled" resultType="com.xxy.pojo.Team">
select * from team where ${column}=#{columnValue}
</select>
3,测试方法
@Test
public void test06(){
System.out.println("根据名称查询:");
List<Team> teams = teamMapper.queryByFiled("teamName","钟离");
teams.forEach(team -> System.out.println(team));
System.out.println("根据位置查询:");
List<Team> teams2 = teamMapper.queryByFiled("location","璃月");
teams2.forEach(team -> System.out.println(team));
}
十二,输出映射resultType
执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名。如果返回的是集合,设置的是集合元素的类型,而不是集合本身。
resultType 和 resultMap,不能同时使用。
1,输出简单类型
2,输出pojo类型
3,输出map类型
当我们只需要查询表中几列数据的时候可以将sql的查询结果作为Map的key和value。一般使用的是Map<Object,Object>.
Map 作为接口返回值,sql 语句的查询结果最多只能有一条记录。大于一条记录会抛出TooManyResultsException异常。
如果有多行,使用List<Map<Object,Object>>.
十三,输出映射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.xxy.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(){
List<Team> teams = teamMapper.queryAll2();
teams.forEach(team-> System.out.println(team));
}
十四,属性名称与列名不一致的解决方案
1,使用列别名和resultType
<?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.xxy.mapper.UsersMapper">
<!--方式1:resultType中的实体类的属性作为查询语句中的别名,让别名和属性保持一致-->
<select id="queryById" resultType="com.xxy.pojo.Users">
select user_id as userId,user_name as userName,user_age as userAge from users where user_id=#{id};
</select>
</mapper>
2,使用resultMap(常用!)
<!--方式2:通过resultMap自行映射-->
<select id="queryById2" resultMap="baseMap">
select * from users where user_id=#{id};
</select>
<resultMap id="baseMap" type="com.kkb.pojo.Users">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_age" property="userAge"/>
</resultMap>
十五,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">
1,配置的内容
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties--属性:加载外部的配置文件,例如加载数据库的连接信息
Settings--全局配置参数:例如日志配置
typeAliases--类型别名
typeHandlers----类型处理器
objectFactory-----对象工厂
Plugins------插件:例如分页插件
Environments----环境集合属性对象
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
Mappers---映射器:注册映射文件用
2,属性properties
属性可以在外部进行配置,并可以进行动态替换。我们既可以在 properties 元素的子元素中设置(例如DataSource节点中的properties节点),也可以在 Java 属性文件中配置这些属性。
数据源中有连接数据库的四个参数数据,我们一般都是放在专门的属性文件中,mybatis的全局配置文件直接从属性文件中读取数据即可。
1,在 resources 目录创建 jdbc.properties 文件,文件名称可以自定义。
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
jdbc.username=root
jdbc.password=root
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>
3,设置settings
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为.例如我们配置的日志就是应用之一。其余内容参考https://mybatis.org/mybatis-3/zh/configuration.html#settings
<!--配置日志-->
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4,类型别名typeAliases
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
一般是给resultType、parameterType取别名,其他地方的全限定名仍需保持原样。
4.1 Mybatis中已经支持的别名
4.2 自定义别名
推荐写法:批量定义别名。扫描指定包下的所有类,同时别名定义为类名,别名的首字母大小写都可以
<!--自定义类型别名-->
<typeAliases>
<!--对单个的实体类定义别名-->
<typeAlias type="com.xxy.pojo.Team" alias="Team"/>
<!--推荐写法:批量定义别名:扫描指定包下的所有类,同时别名定义为类名,别名的首字母大小写都可以-->
<package name="com.kkb.pojo"/>
</typeAliases>
5,映射器Mappers
5.1 使用相对于类路径的资源引用
<mapper resource=""/>
使用相对于类路径的资源,从 classpath 路径查找文件 例如:
<mapper resource="com/kkb/mapper/TeamMapper.xml" />
5.2 使用映射器接口实现类的完全限定类名
<mapper class=""/>
使用mapper接口的完全限定名。要求:接口和映射文件同包同名
<mapper class="com.kkb.mapper.GameRecordMapper"/>
5.3 将包内的映射器接口实现全部注册为映射器--推荐
<package name=""/>
指定包下的所有Mapper接口。 注意:此种方法要求 Mapper接口名称和 mapper 映射文件名称相同,且在同一个目录中。
<package name="com.kkb.mapper"/>
6,dataSources标签
Mybatis 中访问数据库支持连接池技术,而且是采用的自己的连接池技术。
在 Mybatis 的 mybatis.xml配置文件中,通过来实现 Mybatis 中连接池的配置。MyBatis 在初始化时,根据的 type 属性来创建相应类型的的数据源 DataSource。
Mybatis 的数据源分为三类:
- UNPOOLED: 不使用连接池的数据源
- POOLED:使用连接池的数据源
- JNDI:使用JNDI实现的数据源
前两个数据源都实现javax.sql.DataSource接口
7,事务
7.1 默认需要手动提交事务的
Mybatis 框架是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC 的 Connection对象的 commit(), rollback() 。Connection 对象的 setAutoCommit()方法来设置事务提交方式。
<transactionManager type="JDBC"/>
该标签用于指定 MyBatis所使用的事务管理器。MyBatis 支持两种事务管理器类型:JDBC 与 MANAGED。
- JDBC:使用JDBC的事务管理机制,通过Connection对象的 commit()方法提交,通过rollback()方法 回滚。默认情况下,mybatis将自动提交功能关闭了,改为了手动提交,观察日志可以看出,所以我们在程序中都需要自己提交事务或者回滚事务。
- MANAGED:由容器来管理事务的整个生命周期(如Spring容器)。
7.2 自动提交事务
SqlSessionFactory的openSession重载方法,可以设置自动提交的方式。
如果sqlSession = SqlSessionFactory.openSession(true);参数设置为true,再次执行增删改的时候就不需要执行session.commit()方法,事务会自动提交。
十六,Mybatis关系映射
1,对一映射
球员对应一个球队(属性中添加球队实体)
1.1 添加实体类
public class Player {
private Integer playerId;
private String playerName;
private Integer playerNum;
private Integer teamId;
//关系字段:多个球员可以属于同一个球队
//多方(球员)持有一方(球队)的对象
private Team team1;
private Team team2;
private Team team3;
// 省略getter和setter方法
}
1.2 mapper接口
public interface PlayerMapper {
Player queryById(int playerId);
Player queryById1(int playerId);
Player queryById2(int playerId);
Player queryById3(int playerId);
}
1.3 映射方式
方式1:通过关联对象打点调用属性的方式
<select id="queryById1" resultMap="joinTeamResult1">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
<!-- 这里的team1指Player类中的属性 -->
<result column="teamId" property="team1.teamId"></result>
<result column="teamName" property="team1.teamName"></result>
<result column="location" property="team1.location"></result>
<result column="createTime" property="team1.createTime"></result>
</resultMap>
要求:
- 两表的连接查询
方式2:直接引用关联对象的Mapper映射
<select id="queryById2" resultMap="joinTeamResult2">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<!-- 之前编写过TeamMapper.xml,这里直接引用那里的baseResultMap映射 -->
<association property="team2" javaType="Team"
resultMap="com.kkb.mapper.TeamMapper.baseResultMap"/>
</resultMap>
要求:
- 1、两表的连接查询
- 2、关联对象中已经存在被引用的resultMap
方式3:直接引用关联对象的单独查询的方法
<select id="queryById3" resultMap="joinTeamResult3">
<!-- 这里不再需要连接查询 -->
select * from player where playerId=#{id}
</select>
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<!-- 引用了TeamMapper.xml中的查询节点select,相当于执行第二次sql查询 -->
<association property="team3" javaType="Team"
select="com.kkb.mapper.TeamMapper.queryById" column="teamId"/>
</resultMap>
要求:
- 1、不需要两表的连接查询
- 2、关联对象中已经存在被引用的查询方法
完整的PlayerMapper.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.kkb.mapper.PlayerMapper">
<select id="queryByTeamId" resultMap="baseResultMap">
select * from player where teamId=#{id}
</select>
<select id="queryById" resultMap="baseResultMap">
select * from player where playerId=#{id}
</select>
<resultMap id="baseResultMap" type="Player">
<id column="playerId" property="playerId"/>
<result column="playerName" property="playerName"/>
<result column="playerNum" property="playerNum"/>
<result column="teamId" property="teamId"/>
</resultMap>
<!--方式1: 通过对象打点属性直接映射列名
要求:必须连接查询
一般会自定义结果映射-->
<select id="queryById1" resultMap="joinTeamResult1">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult1" type="Player" extends="baseResultMap">
<!-- 这里的team1指Player类中的属性 -->
<result column="teamId" property="team1.teamId"></result>
<result column="teamName" property="team1.teamName"></result>
<result column="location" property="team1.location"></result>
<result column="createTime" property="team1.createTime"></result>
</resultMap>
<!--方式2:直接引用关联对象的Mapper映射
要求:必须连接查询-->
<select id="queryById2" resultMap="joinTeamResult2">
SELECT * FROM `player` p INNER JOIN team t
on t.teamId=p.teamId
where playerid=#{id}
</select>
<resultMap id="joinTeamResult2" type="Player" extends="baseResultMap">
<!-- 之前编写过TeamMapper.xml,这里直接引用那里的baseResultMap映射 -->
<association property="team2" javaType="Team"
resultMap="com.kkb.mapper.TeamMapper.baseResultMap"/>
</resultMap>
<!--方式3:使用关联对象的单独的查询语句
要求:不需要连接查询
需要关联对象中存在对应的查询语句-->
<select id="queryById3" resultMap="joinTeamResult3">
select * from player where playerId=#{id}
</select>
<resultMap id="joinTeamResult3" type="Player" extends="baseResultMap">
<!-- 引用了TeamMapper.xml中的查询节点select,相当于执行第二次sql查询 -->
<association property="team3" javaType="Team"
select="com.kkb.mapper.TeamMapper.queryById" column="teamId"/>
</resultMap>
</mapper>
2,对多映射
一个球队含有多名球员(属性中添加球员列表)
2.1 修改实体类Team.java
public class Team implements Serializable {
private Integer teamId;
private String teamName;
private String location;
private Date createTime;
//关系字段:一个球队可以拥有多个球员
//一方(球队)持有多方(球员)的集合
private List<Player> playerList1;
private List<Player> playerList2;
}
2.2 接口中添加方法
TeamMapper.java
public interface TeamMapper {
Team queryById1(int teamId);
Team queryById2(int teamId);
PlayerMapper.java(为映射方式二做准备)
public interface PlayerMapper {
List<Player> queryByTeamId(int teamId);
2.3 映射方式
方式一:连接查询+引用关联对象的结果映射
<select id="queryById1" resultMap="joinPlayMap1">
select * from team t inner join player p
on t.teamId=p.teamId where t.teamId=#{id}
</select>
<!--collection:多对映射的节点
property:实体类中要查询的集合属性
javaType:集合类型
ofType:集合类型中元素的类型
resultMap-->
<resultMap id="joinPlayMap1" type="Team" extends="baseResultMap">
<collection property="playerList1" javaType="arraylist" ofType="Player"
resultMap="com.kkb.mapper.PlayerMapper.baseResultMap"/>
</resultMap>
方式二:引用关联对象的单独查询的方法
<select id="queryById2" resultMap="joinPlayMap2">
select * from team where teamId=#{id}
</select>
<resultMap id="joinPlayMap2" type="Team" extends="baseResultMap">
<collection property="playerList2" javaType="arraylist" ofType="Player"
select="com.kkb.mapper.PlayerMapper.queryByTeamId" column="teamId"/>
</resultMap>
十七,动态SQL
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。
利用动态 SQL,可以彻底摆脱这种痛苦。使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
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
public class QueryTeamVO {
private String name;
private Date beginTime ;
private Date endTime;
private String location;
}
3,TeamMapper.java接口添加
List<Team> queryByVO(QueryTeamVO vo);
4,TeamMapper.xml映射文件添加
为避免SQL注入问题,尽可能不使用字符串替换功能${},而是采用concat方法拼接字符串
<!--多条件查询:
模糊查询的写法可以使用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" useCache="false">
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,测试方法
@Test
public void test1(){
QueryTeamVO vo=new QueryTeamVO();
vo.setName("人");
vo.setEndTime(new Date());
vo.setLocation("加利福尼亚州洛杉矶");
List<Team> teams = teamMapper.queryByVO(vo);
for (Team team : teams) {
System.out.println(team);
}
}
2,set标签在update中的使用
直接用对象更新数据库中的记录时,未设定值的属性可能会将数据库表中原有的字段覆盖掉。因此通常的做法是先查询记录,保存到对象中,再更改对象中的部分属性,最后执行更新语句。
1,原先的更新方法
<update id="update" parameterType="com.kkb.pojo.Team">
update team set teamName=#{teamName},location=#{location},createTime=#{createTime}
where teamId=#{teamId}
</update>
2,使用set标签构建动态的SQL语句
<update id="update1" parameterType="com.kkb.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>
</set>
where teamId=#{teamId}
</update>
3,forEach标签
3.1 批量添加
<!--批量添加-->
<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 test3(){
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);
}
teamMapper.addList(list);
MybatisUtil.getSqlSession().commit();
}
3.2 批量删除
<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 test4() {
List<Integer> list = new ArrayList<>();
list.add(1109);
list.add(1110);
list.add(1111);
teamMapper.delList(list);
MybatisUtil.getSqlSession().commit();
}
十八,分页插件的使用
1,在pom.xml中引入jar依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
2,在mybatis全局配置文件中添加插件配置
注意plugins节点位置(environments之前)
<!--配置分页插件-->
<plugins>
<!--5.0版本之前使用的PageHelper,5.0之后使用PageInterceptor-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--<property name="reasonable" value="true"/>-->
</plugin>
</plugins>
3,使用插件
@Test
public void test5() {
// PageHelper.startPage 必须紧邻查询语句,而且只对第一条查询语句生效(拦截器,拦截查询语句)
PageHelper.startPage(2,5);
List<Team> teams = teamMapper.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);
}
}
4,部分源码
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缓存
1,缓存的作用
缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。将经常查询的数据存在缓存(内存)中,用户查询该数据的时候不需要从磁盘(关系型数据库文件)上查询,而是直接从缓存中查询,提高查询效率,解决高并发问题。
MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
Mybatis的缓存结构体系:
2,一级缓存:自动开启,SqlSession级别的缓存
在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,将不再从数据库查询,从而提高查询效率。
当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。
Mybatis默认开启一级缓存,存在内存中(本地缓存)不能被关闭,可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
2.1 一级缓存分析
当用户发起第一次查询team=1001的时候,先去缓存中查找是否有team=1001的对象;如果没有,继续向数据中发送查询语句,查询成功之后会将teamId=1001的结果存入缓存
中;
当用户发起第2次查询team=1001的时候,先去缓存中查找是否有team=1001的对象,因为第一次查询成功之后已经存储到缓存中,此时可以直接从缓存中获取到该数据,意味
着不需要再去向数据库发送查询语句。
如果SqlSession执行了commit(有增删改的操作),此时该SqlSession对应的缓存区域被整个清空,目的避免脏读。
前提:SqlSession未关闭。
测试:
//测试一级缓存:自动开启,sqlSession级别的缓存
@Test
public void test1() {
Team t1=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第一次查询,先查缓存,此时缓存中没有,继续向数据库发送查询语句
System.out.println(t1);//查询完毕之后数据被自动存入缓存区域
Team t2=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存中已经有了该数据,可以直接获取,不需要发送查询语句
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
sqlSession=MybatisUtil.getSqlSession();//再次获取连接,此时缓存为空
Team t3=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//新连接下第一次查询,肯定发送查询语句
System.out.println(t3);//查询完毕之后数据被自动存入缓存区域
int num=sqlSession.delete("com.kkb.mapper.TeamMapper.del",10000);
sqlSession.commit();//提交之后缓存被整个清空
System.out.println("删除结果:"+num);
Team t4=sqlSession.selectOne("com.kkb.mapper.TeamMapper.queryById",1001);//第二次查询,因为缓存已经被上一次的提交清空了,所以还是需要发送查询语句
System.out.println(t4);
sqlSession.close();
}
没有重复发送sql语句,说明缓存有存储;
只要提交删除语句,即使没有符合条件的记录,缓存仍然会清空;
2.2 清除缓存的方法
- 1、 session.clearCache( ) ;
- 2、 execute update(增删改) ;
- 3、 session.close( );
- 4、 xml配置 flushCache="true" ;
- 5、 rollback;
- 6、 commit。
3,二级缓存:Mapper级别的缓存
多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。
不同的sqlSession两次执行相同namespace下的sql语句参数相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。
如果缓存中有数据就不用从数据库中获取,大大提高系统性能。
二级缓存原理图:
3.1 二级缓存使用步骤
二级缓存是mapper范围级别的,默认不启用。
1,在Mybatis框架的全局配置文件中开启二级缓存
2,在需要二级缓存的Mapper中添加缓存标志
3,实体类必须实现Serializable接口
4,测试
如果两个session不是从同一个Factory获取,那么二级缓存将不起作用。
@Test
public void test2() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
Team t1 = sqlSession1.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);//先查缓存,没有,先数据库,查询完毕写入二级缓存
System.out.println(t1);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
Team t2 = sqlSession2.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);//先查缓存,有,直接获取,不需要查询数据库
System.out.println(t2);
MybatisUtil.closeSqlSession();//关闭连接,一级缓存清空,二级缓存存在
SqlSession sqlSession3 = MybatisUtil.getSqlSession();
int num = sqlSession3.delete("com.kkb.mapper.TeamMapper.del", 10000);//删除成功
System.out.println("删除的结果:" + num);
sqlSession3.commit();//提交之后清空二级缓存
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
SqlSession sqlSession4 = MybatisUtil.getSqlSession();
Team t3 = sqlSession4.selectOne("com.kkb.mapper.TeamMapper.queryById", 1001);先查缓存,曾经有,但是上一个提交已经清空了缓存,所以只能去数据库中查询,查询完毕写入二级缓存
System.out.println(t3);
MybatisUtil.closeSqlSession();//关闭连接,缓存清空
}
3.2 禁止二级缓存
对于变化比较频繁的SQL,可以禁用二级缓存。
在开始了二级缓存的XML中对应的statement中设置useCache=false禁用当前Select语句的二级缓存。
useCache默认值是true。对于一些很重要的数据尽量不放在二级缓存中。
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(可读/可写)的缓存,意味着检索对象不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
**/
如果想在命名空间中共享相同的缓存配置和实例(使用其他mapper.xml文件中缓存配置),可以使用cache-ref 元素来引用另外一个缓存。
// 引用TeamMapper命名空间中的cache。
<cache-ref namespace="com.kkb.mapper.TeamMapper" />
二十,Mybatis反向生成插件
反向生成即,自动生成实体类、mapper接口、映射文件
1,使用方法
1,在pom.xml文件中的中中添加如下插件配置
<!--反向生成插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<configuration>
<!--配置文件的路径-->
<!-- 需要在对应位置手动创建generatorConfig.xml -->
<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>
2,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://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT"
userId="root"
password="root">
</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开启
-->
<!-- 这里表示严格按照数据库表中的命名方式,由于数据库中大小写不敏感,所以为了符合Java的命名规范,建议添加 -->
<!-- 如果数据库中表的命名用下划线分隔,这里就不需要添加,会自动生成符合Java规范的名称 -->
<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>
<!-- 这里如果不设置为false,默认自动生成数据库表的基础操作,比如查询所有、条件查询等 -->
<!--<table tableName="Team"
enableCountByExample="false"
enableUpdateByExample="false"
enableUpdateByPrimaryKey="false"
enableDeleteByExample="false"
enableDeleteByPrimaryKey="false"
enableSelectByExample="false"
selectByExampleQueryId="false">
<property name="useActualColumnNames" value="true"/>
</table>-->
</context>
</generatorConfiguration>
3,运行插件
注意只能运行一次(),运行完毕显示BUILD SUCCESS即为成功。若想重新生成,需要将已生成的内容删除
4,反向生成内容的使用
- 使用之前需要在mybatis.xml配置文件中注册;
- 增删改仍需要手动提交事务;
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("lina-test");
team.setLocation("bj");
int i = mapper.insert(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,1100);
//排序
example.setOrderByClause("teamName desc");
List<Team> teams = mapper.selectByExample(example);
for (Team team : teams) {
System.out.println(team);
}
}
}