5. mybatis
5.1 搭建mybatis
5.1.1 开发环境
mysql 8的驱动类使用:com.mysql.cj.jdbc.Driver
mysql 8的url使用:jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
5.1.2 创建maven工程
创建空项目 ----> New一个maven模板 -----> 在设置中重写maven的本地仓库地址和setting.xml
然后在pom.xml文件中将打包方式设置为jar
<packaging>jar</packaging>
引入依赖
<dependencies>
<!-- mybatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--junit测试-->
<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.29</version>
</dependency>
</dependencies>
5.1.3 创建mybatis的核心配置文件
在resource下创建一个xml文件,名字随意,这里取 mybatis-config.xml。在里面添加下面的代码(从mybatis官网复制并修改相应的属性值)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--配置连接数据库环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入mybatis的映射文件-->
<mappers>
<mapper resource=""/>
</mappers>
</configuration>
5.1.4 创建mapper接口
mybatis中的mapper相当于dao
package com.spark.mapper;
public interface UserMapper {
int insertUser();
}
5.1.5 创建mybatis映射文件
ORM:对象关系映射
类 -> 表
属性 -> 字段
对象 -> 行
-
映射命名规则:表对应实体类类名+Mapper.xml
比如:表t_user,映射的实体类为User,对应的映射文件为UserMapper.xml
因此一个映射文件对应一张表,一个实体类
映射文件用于编写sql,操作数据
文件存放在resource/mappers目录下
-
在映射文件中
-
mapper接口的全类名要和映射文件的namespace一致
<mapper namespace="com.spark.mapper.UserMapper"> </mapper>
-
mapper接口中的方法名要和映射文件中的sql的id保持一致
<!--int insertUser();--> <insert id="insertUser"> insert into user values (null,'hutao',23,0); </insert>
-
然后在之前创建的mybatis-config.xml文件中的mapper标签中引入映射文件
<!--引入mybatis的映射文件--> <mappers> <mapper resource="mappers/UserMapper.xml"/> </mappers>
-
5.1.6 用mybatis实现在数据库中添加用户信息
我们在Test目录下的Java文件夹中创建测试类
package com.spark;
import com.spark.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisTest {
@Test
public void testInsert() throws IOException {
//获取核心配置文件的输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取sql的会话对象SqlSession,是MyBatis操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取UserMapper的代理实现类对象(实现类需要创建对象,用代理对象就不需要我们另外创建了。)
//(同时,在UserMapper.xml文件中我们已经重写了UserMapper接口的方法,所以可以直接用代理对象调用。这就是映射的作用)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用Mapper接口中的方法,实现添加用户信息的功能
int result = mapper.insertUser();
System.out.println("结果:"+result);
//提交事务
sqlSession.commit();//不添加的话,事务会被回滚,但是可以在控制台看到结果:1
//关闭会话对象
sqlSession.close();
}
5.1.7 mybatis优化
第一处优化:
对于下面的代码:
//获取核心配置文件的输入流
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取sqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
//获取sql的会话对象SqlSession,是MyBatis操作数据库的对象
SqlSession sqlSession = sqlSessionFactory.openSession();
由于这是一种固定的代码,我们可以创建一个工具类,将上述代码在工具类中定义成一个方法,以后要使用直接调用即可
第二处优化(其实也不算优化)
我们可以不用写:
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用Mapper接口中的方法,实现添加用户信息的功能
int result = mapper.insertUser();
System.out.println("结果:"+result);
直接利用sqlSession中的方法:
//通过找到唯一标识找到sql并执行,唯一标识是namesapce.sqlId
int res = sqlSession.insert("com.spark.mapper.UserMapper.insertUser");
System.out.println(res);
第三处优化
我们最后手动写了一个事务提交
sqlSession.commit();
如果想要自动提交的话,可以在开启会话的时候就设置
//这个openSession()是个有参方法,如果加入true,就会改成自动提交
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession = sqlSessionFactory.openSession(true);
第四处优化
可以添加日志功mybatis-config.xml文件中引入依赖)
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
这个依赖要写在configration标签中的,且放在最上方
5.1.8 日志级别
fatal > error > warn > info(信息) > debug
从左往右,打印的日志内容越详细
5.1.9 mybatis实现增删改查
对于增删改比较简单。
首先在UserMapper接口中定义方法,没有返回值。而查询数据需要返回值,单条查询返回User类型,多条查询返回以User为泛型的集合类型
package com.spark.mapper;
import com.spark.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 添加用户信息
* @return
*/
int insertUser();
/**
* 修改用户信息
* @return
*/
void updateUser();
/**
* 删除用户信息
*/
void deleteUser();
/**
* 根据id查询用户
* @return
*/
User getUserById();
/**
* 查询所有用户
* @return
*/
List<User> getAllUser();
}
然后在UserMapper.xml中编写sql语句。查询操作要多写一步。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.spark.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into user values (null,'hutao',23,0);
</insert>
<!--void updateUser();-->
<update id="updateUser">
update user set name = 'qiqi' where id = 3;
</update>
<!--void deleteUser();-->
<delete id="deleteUser">
delete from user where id = 3;
</delete>
<!--User getUserById();-->
<!--
resultType:设置结果类型,即查询的数据要转换为java类型
resultMap:自定义映射,处理多对一或一对多的映射关系
这两个属性只能写一个
-->
<select id="getUserById" resultType="com.spark.pojo.User">
select * from user where id = 1;
</select>
<!--List<User> getAllUser();-->
<select id="getAllUser" resultType="com.spark.pojo.User">
select * from user;
</select>
</mapper>
最后在测试类中写测试方法
package com.spark;
import com.spark.mapper.UserMapper;
import com.spark.pojo.User;
import com.spark.utils.SqlSessionUtil;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
@Test
public void testUpdate(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser();
sqlSession.close();
}
@Test
public void testDelete(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser();
sqlSession.close();
}
@Test
public void testSelect(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById();
System.out.println(userById);
sqlSession.close();
}
@Test
public void testAllSelect(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.getAllUser();
list.forEach(System.out::println);
sqlSession.close();
}
}
5.2 mybatis核心配置
5.2.1 environment(mybatis-config.xml)
<!--配置连接数据库环境-->
<!--
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
environment:设置一个具体的数据库的连接环境
属性:
id:环境设置的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理器
属性:
type:设置事务管理的方式
type="JDBC/MANAGED"
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
datasource:设置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOKED|JNDI"
POOLED:表示使用数据库连接池(使用数据库连接池的话,每一次登录都会使用上一次用过的数据源)
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
5.2.2 properties
我们是将数据源放在mybatis映射文件中指定的,但是这样不便于管理,我们可以利用jdbc的方式,将数据源都放在properties文件中,然后在mybatis映射文件中引入properties资源,再把原来property标签中的值改成==${key}==的形式。
来吧,在resource文件下中创建一个jdbc.properties文件
#driver=com.mysql.cj.jdbc.Driver
#建议加上前缀jdbc,防止用错数据源
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
这里的driver、url、password、username要跟property中的对应
然后在映射文件中引入该properties资源
<!--引入properties文件,此后就可以在当前文件中使用${key}的方式访问value-->
<properties resource="jdbc.properties"/>
最后,将原来的value值改成 ${key} 的形式
<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>
5.2.3 typeAliases
这是mybatis-config.xml中的标签,用来取别名。比如全类名太长,想给他取一个简短的名字
<typeAliases>
<typeAlias type="com.spark.pojo.User" alias="User"></typeAlias>
</typeAliases>
<!--
type:需要起别名的类型
alias:取的别名
-->
<!-- 如果没有指定alias的值,默认为类名,而且不区分大小写,在这里就是User/user。上下两个是一样的 -->
<typeAliases>
<typeAlias type="com.spark.pojo.User"></typeAlias>
</typeAliases>
这样的话,我们就可以在UserMapper.xml文件中,用这个别名,来替换全类名
<select id="getAllUser" resultType="User">
select * from user;
</select>
另外还有种比较方便的方式,就是用package标签
<typeAliases>
<!-- 通过包名设置别名,则指定包下的所有类型都有默认的别名(即类名) -->
<package name="com.spark.pojo"/>
</typeAliases>
注意:别名只能在mybatis的范围中使用
5.2.4 mappers
之前我们在引入mybatis映射文件时,是在mappers标签下的mapper标签中引入的。
<!--引入mybatis的映射文件。mappers包下的UserMapper.xml文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
跟取别名类似,我们也可以用打包的方法引入。但这里打包有条件,就是:映射文件必须跟mapper接口同包同名
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<package name="com.spark.mapper"/>
</mappers>
乍一看,这两个文件好像并没有在一个目录里,那么让它们同包有意义吗?
当然有,其实在实际的目录中,它们是在同一个文件夹里的。
5.2.5 创建自定义模板
我们辛辛苦苦敲了这么多配置文件,在多数情况下都是能够以它们作为基础来添加代码的。所以我们可以将这些文件,借助IDEA的自定义模板功能来用它们构建模板。
这样新建模板就可以直接得到上述代码文件
5.3 mybatis参数
5.3.1 获取参数的两种方式
一种是 ${} 一种是 #{}
${} 本质上是字符串拼接,使用时需要手动加引号
#{} 本质上是占位符,这个不需要
<select id="getUserByUserName" resultType="User">
select * from user where name = #{name};
select * from user where name = '${name}';
</select>
<!--
#{name}在sql语句编译时,会被替代成问号(?),在传递参数的时候,
会根据传来的数据类型来自动添加引号
${name}则不会,它传到什么就是什么,不会加引号
另外,两者的大括号中的内容可以随便取。不过最好按规范来。
以上都是mapper接口方法的参数为单个字面量类型的情况
-->
这是第一种
这是第二种
5.3.2 多个字面量参数
mapper接口方法的参数为多个字面量时
此时mybatis会将参数放在map集合中,以两种方式存储数据
第一种是以arg0、arg1...为键,参数为值
第二种是以param1、param2...为键,参数为值
因此只需要通过${}和#{}访问map集合的键,就可以获取相对应的值
<!--User findUser(String name,Integer gender);-->
<select id="findUser" resultType="User">
<!--select * from user where name = #{param1} and gender = #{param2};-->
<!--select * from user where name = #{arg0} and gender = #{arg1};-->
select * from user where name = '${arg0}' and gender = '${arg1}';
</select>
既然是存在map集合中,我们可以将数据放在我们自己创建的map集合中啊。
在UserMapper中创建一个接口方法,参数为map集合
/**
* 用自己的map
* @param map
* @return
*/
User findUserByMap(Map map);
在测试方法中,自己设置集合的键,并将键的值放入集合中,然后调用mapper方法,将集合传递给参数
@Test
public void findUserByMap() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("name","hutao");
map.put("gender",0);
User user = mapper.findUserByMap(map);
System.out.println(user);
}
在映射文件中,参数获取就可以跟单个字面量一样使用了
<!--User findUserByMap(Map map);-->
<select id="findUserByMap" resultType="User">
<!--select * from user where name = #{name} and gender = #{gender};-->
select * from user where name = '${name}' and gender = '${gender}';
</select>
如果mapper的接口方法参数为实体类的参数类型,只需要通过#{}和${}访问实体类的属性名,就可以获得对应的值。
/**
* 插入数据
* @param user
*/
void insertUser(User user);
<!--void insertUser(User user);-->
<insert id="insertUser">
insert into user values (null,#{name},#{gender},#{age});
</insert>
@Test
public void TestInsertUser() {
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(null,"胡桃",0,17);
mapper.insertUser(user);
System.out.println(user);
}
5.3.3 @param注解
它的作用就是修改mybatis中map集合中键的名字。用在接口方法的参数中。
User findUser(@Param("name") String name, @Param("gender")Integer gender);
<!--User findUser(String name,Integer gender);-->
<select id="findUser" resultType="User">
<!--select * from user where name = #{param1} and gender = #{param2};-->
<!--select * from user where name = #{arg0} and gender = #{arg1};-->
<!--select * from user where name = '${arg0}' and gender = '${arg1}';-->
select * from user where name = #{name} and gender = #{gender} ;
</select>
用了这个注解后,其实还是可以用param1、param2…这样的为键,参数为值。arg0、arg1…是用不了了。
5.3.4 mybatis取别名
我写了一个计数操作
/**
* 返回用户数量
* @return
*/
Integer UserCount();
<select id="UserCount" resultType="int">
select count(*) from user;
</select>
@Test
public void TestUserCount(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer count = mapper.UserCount();
System.out.println(count);
}
运行时没有报错,结果也正确。但为什么?
在resultType中我并没有写哪种类型,照理说整数类型的数据不应该在java.lang.Integer中吗?
其实这是mybatis的别名机制
5.3.5 以map作为结果类型
单条数据的情况
之前我们的resultType中通常都是User。也就是说查询的数据会以User的属性值返回,即使有些属性没有赋值,它依然会以null的值返回,而空值有时候我们并不需要。
<!--User findUser(String name,Integer gender);-->
<select id="findUser" resultType="User">
<!--select * from user where name = #{param1} and gender = #{param2};-->
<!--select * from user where name = #{arg0} and gender = #{arg1};-->
<!--select * from user where name = '${arg0}' and gender = '${arg1}';-->
select * from user where name = #{name} and gender = #{gender} ;
</select>
@Test
public void testFindUser(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUser("hutao",0);
System.out.println(user);
}
这个时候我们可以将map作为返回值(这样只有不为null的字段才会放进map集合中)
<!--void selectUserByIdToMap();-->
<select id="selectUserByIdToMap" resultType="map">
select * from user where id = #{id}
</select>
Map<String,Objects> selectUserByIdToMap(@Param("id") Integer id);
@Test
public void TestSelectUserByIdToMap(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Objects> map = mapper.selectUserByIdToMap(1);
System.out.println(map);
}
多条数据的情况
我想把表格中的数据,都用resultType为map的形式查询出来。首先要想到多条数据肯定是放在集合中的,存放集合的集合,容易想到用list
第一种方式:
List<Map<String,Object>> selectAllUserToMap();
<select id="selectAllUserToMap" resultType="map">
select * from user;
</select>
@Test
public void TestSelectAllUserToMap(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<Map<String, Object>> maps = mapper.selectAllUserToMap();
maps.forEach(System.out::println);
}
第二种方式:
使用@MapKey注解
//可以将每条数据转换为map集合放在一个大的map集合中。利用MapKey注解将查询的某个字段的值作为大的map的键
@MapKey("id")
Map<String,Object> selectAllUserToMap();
<select id="selectAllUserToMap" resultType="map">
select * from user;
</select>
@Test
public void TestSelectAllUserToMap(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> maps = mapper.selectAllUserToMap();
System.out.println(maps);
}
5.4 特殊SQL语句的执行
5.4.1 模糊匹配
对于下面的模糊匹配,将报错。
List<User> selectByLike(@Param("mohu") String mohu);
<select id="selectByLike" resultType="User">
select * from user where name like '%#{mohu}%'
</select>
// #{}在SQL编译中会被编译成?
// 结果就是'%?%'
@Test
public void selectByLike(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectByLike("h");
list.forEach(System.out::println);
}
解决方法:
第一种(缺点,可以被SQL注入):
<!--将#{}改成${}-->
<select id="selectByLike" resultType="User">
select * from user where name like '%${mohu}%'
</select>
第二种(比较好的):
字符串拼接函数
<select id="selectByLike" resultType="User">
select * from user where name like concat('%',#{mohu},'%');
</select>
5.4.2 批量删除
<delete id="deleteMoreUser" >
delete from user where id in (${ids});
</delete>
输入:num1,num2
得到:delete from user where id in (num1,num2);
5.4.3 动态查询表
/**
* 动态查询表
* @param tableName
* @return
*/
List<User> GetUserList(@Param("tableName")String tableName);
<select id="GetUserLIst" resultType="User">
select * from ${tableName};
</select>
@Test
public void TestGetUserList(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.GetUserList("user");
list.forEach(System.out::println);
}
5.4.4 添加功能获取自增的主键
就是添加用户的时候,你可以知道这是表格中的第几个。
/**
* 添加用户信息并获取添加的主键
* @param user
*/
void insertUser_(User user);
/*
useGeneratedKeys="true"
表示使用自增主键
keyProperty="id"
将主键值赋给id,这个id必须是实体类的自增主键属性
*/
<!--void insertUser_(User user);-->
<insert id="insertUser_" useGeneratedKeys="true" keyProperty="id">
insert into user values(null,#{name},#{age},#{gender});
</insert>
@Test
public void TestInsertUser(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(null,"牵牛星",25,1);
mapper.insertUser_(user);
System.out.println(user);
}
5.5 自定义映射resultMap
5.5.1 映射关系处理
当属性名跟字段名映射关系不一致时
emp_id empId
-
为查询字段设置跟属性名一致的别名
<select id="getEmpByEmpId" resultType="Emp"> select emp_id empId from new_emp where emp_id = #{empId}; </select>
-
在mybatis的核心配置文件中设置一个全局配置,将下划线写法转化为驼峰写法
<setting name="mapUnderscoreToCamelCase" value="true"/>
-
使用resultMap自定义映射处理
<resultMap id="empResultMap" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> </resultMap> <!-- id:自定义映射的唯一标识 type:处理映射关系的实体类型 id:设置主键和属性映射的关系 result:设置普通字段跟属性映射的关系 --> <!--Emp getEmpByEmpId(@Param("id")Integer id); 这里就不需要写resultType了--> <select id="getEmpByEmpId" resultMap="empResultMap"> select * from new_emp where emp_id = #{empId}; </select>
5.5.2 处理多对一的映射关系
假设这里有一份员工表和一份部门表,我想通过员工id查询该员工的基本信息及部门信息。请问该怎么做?
//Emp类的属性
private Integer empId;
private String empName;
private Integer age;
private String gender;
private Dept dept;
//Dept类的属性
private Integer deptId;
private String deptName;
第一种:级联处理
<resultMap id="edResultMap" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<!-- 关键看这里 -->
<result column="dept_id" property="dept.deptId"></result>
<result column="dept_name" property="dept.deptName"></result>
</resultMap>
<!--Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="edResultMap">
select
new_emp.*,new_dept.*
from new_emp
left join new_dept
on new_emp.emp_id = new_dept.dept_id
where new_emp.emp_id = #{empId};
</select>
第二种:association
<resultMap id="edResultMapTwo" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<!--
association:用来处理实体类类型的属性(Dept dept)
property:这个属性的属性名
javaType:该属性的实体类类型
-->
<association property="dept" javaType="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
</association>
</resultMap>
<!--Emp getEmpAndDeptByEmpId(@Param("empId")Integer empId);-->
<select id="getEmpAndDeptByEmpId" resultMap="edResultMapTwo">
select
new_emp.*,new_dept.*
from new_emp
left join new_dept
on new_emp.emp_id = new_dept.dept_id
where new_emp.emp_id = #{empId};
</select>
第三种:分步查询
分步查询就是先查询一个表,获得一个字段,然后根据这个字段去查询另外一个表
作为关联两个表的字段,在部门表跟员工表之间,就是dept_id
首先,我们要先查询员工表:
<!--Emp getEmpAndDeptByStep(@Param("empId")Integer empId);-->
<select id="getEmpAndDeptByStepOne" resultMap="edResultMapByStep">
select * from new_emp where emp_id = #{empId};
</select>
然后去设计第二张表的查询(在deptmapper中写接口方法,在deptmapper.xml中写sql语句)
<mapper namespace="com.spark.mapper.DeptMapper">
<!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
select * from new_dept where dept_id = #{deptId};
</select>
</mapper>
接着设置resultMap
<resultMap id="edResultMapByStep" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<association property="dept"
select="com.spark.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id"
></association>
</resultMap>
<!--
select:分布查询sql的唯一标识
column:根据查询出的某个字段作为分布查询sql的条件
-->
效果就是,执行了两句sql语句
5.5.3 分步查询的好处
延迟加载:
<!--延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
按需加载:可以根据需要来查询表,即使设置了多张表的查询语句,如果说查询的某个字段只需要查询一张表,那么就只会执行那一张表的查询语句
<!--按需加载,该设置默认为false-->
<setting name="aggressiveLazyLoading" value="false"/>
这两个setting要一起使用才有效。
setting是全局设置,按照上面的代码,整个mybatis查询表格的时候,就都会开启延迟加载,如果我想要某个语句立即加载,那该怎么弄。
可以在association中的fetchType属性中设置
<resultMap id="edResultMapByStep" type="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
<association property="dept"
select="com.spark.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="dept_id"
fetchType="eager"
></association>
</resultMap>
5.5.4 处理一对多
根据一个部门,查询它下面的所有员工信息
collection:用于一对多(处理集合类型的属性)
ofType:设置集合类型的属性中存储的数据的类型
<resultMap id="deptAndEmpResultMap" type="Dept">
<id column="dept_id" property="deptId"></id>
<result column="dept_name" property="deptName"></result>
<collection property="emps" ofType="Emp">
<id column="emp_id" property="empId"></id>
<result column="emp_name" property="empName"></result>
<result column="age" property="age"></result>
<result column="gender" property="gender"></result>
</collection>
</resultMap>
<!--Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);-->
<select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
select *
from new_dept
left join new_emp
on new_dept.dept_id = new_emp.dept_id
where new_dept.dept_id = #{deptId}
</select>
分步查询也可以,具体见代码
5.6 动态SQL
随着用户的输入或外部条件的变化而变化的SQL语句,称作动态SQL。之前写的代码中,查询的条件是固定死的,只要有一项不满足就会查不到数据。
<select id = "list" resultType = "com.itheima.pojo.Emp">
select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and entrydate between #{begin} and #{end} order by update_time desc
</select>
//比如下面的方法,尽管要查询的表中只有一个人的名字含有“张”,但由于后面的字段为null,无法匹配。所以查询不到结果。
@Test
public void testList(){
List<Emp> empList = empMapper.list("张", null, null, null);
System.out.println(empList);
}
此时,我们需要用到动态SQL。
5.6.1 if标签
<!--
<if>:用于条件判断。使用test属性进行判断,如果条件为true,则拼接SQL。比如下面的例子
如果name不是null,就执行下面的SQL语句。
-->
<if test = "name!=null">
name like concat('%',#{name},'%')
</if>
<!-- 用if标签改造后:-->
<select id="list" resultType="com.itheima.pojo.Emp">
select *
from emp
where
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
order by update_time desc
</select>
//但是如果执行下面的方法,将会报错
@Test
public void testList(){
List<Emp> empList = empMapper.list(null, (short)1, null, null);
System.out.println(empList);
}
可以看看哪里报了个错:
报错的位置靠近“and gender = 1”,我们再来看下xml代码,发现如果执行了上面的方法,那么SQL语句中就多了个and,因为我们将name赋值为null,在xml的if标签中,就会跳过 name like concat(‘%’, #{name}, ‘%’),转而执行 and gender = #{gender},显然在and前面没有连接的字段,出现了语法错误。但是我们又不能将and删去,删去之后 ==List empList = empMapper.list(“Tom”, (short)1, null, null);==又会报错。此时需要想办法解决。
<select id="list" resultType="com.itheima.pojo.Emp">
select *
from emp
where
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
order by update_time desc
</select>
5.6.2 where标签
XML提供了where标签替换where关键字来解决上面的问题。
where标签的作用:
- 根据where中的子标签动态判断条件,如果子标签里的条件都不成立,就不会生成where关键字。如果有一个条件成立,就会生成where关键字。
- 去掉sql语句中多余的 and 或 or
<select id="list" resultType="com.itheima.pojo.Emp">
select *
from emp
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
5.6.3 set标签
对于下面的代码,在执行更新操作时,如果有些字段没有赋值的话,在表中的对应字段会变成null。
@Test
public void testUpdate2(){
//构造员工对象
Emp emp = new Emp();
emp.setId(19);
emp.setUsername("Tom222");
emp.setName("汤姆222");
emp.setGender((short)1);
emp.setUpdateTime(LocalDateTime.now());
//执行更新员工操作
empMapper.update2(emp);
}
在XML中使用if标签就可以很好地解决。
<update id="update2">
update emp
set
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="image != null">image = #{image},</if>
<if test="job != null">job = #{job},</if>
<if test="entrydate != null">entrydate = #{entrydate},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateTime != null">update_time = #{updateTime}</if>
where id = #{id}
</update>
但是又遇到一个问题,如果赋值语句是下面的的话,又会出现语法错误
@Test
public void testUpdate2(){
//构造员工对象
Emp emp = new Emp();
emp.setId(19);
emp.setUsername("Tom222333");
//执行更新员工操作
empMapper.update2(emp);
}
结果发现 ==where id === 的前面多了个逗号。
我们再来看一眼XML配置文件中的代码。我们这次只修改了第一个字段,所以只执行了第一个if标签中的SQL语句,后面if标签中的SQL语句就直接略过了,因此会执行到 username = #{username},,就是这里多了个逗号。
<update id="update2">
update emp
set
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="image != null">image = #{image},</if>
<if test="job != null">job = #{job},</if>
<if test="entrydate != null">entrydate = #{entrydate},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateTime != null">update_time = #{updateTime}</if>
where id = #{id}
</update>
XML也提供了set标签来代替set关键字解决这个问题。
set标签作用:
- 将所有字段进行包裹
- 去除多余的逗号(用在update中)
<update id="update2">
update emp
<set>
<if test="username != null">username = #{username},</if>
<if test="name != null">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="image != null">image = #{image},</if>
<if test="job != null">job = #{job},</if>
<if test="entrydate != null">entrydate = #{entrydate},</if>
<if test="deptId != null">dept_id = #{deptId},</if>
<if test="updateTime != null">update_time = #{updateTime}</if>
</set>
where id = #{id}
</update>
5.6.4 foreach标签(批量操作)
-
SQL语句
delete from emp where id in (18,19,20);
-
接口
public void deleteByIds(List<Integer> ids);
-
XML
<!--批量删除员工 (18,19,20)--> <!-- collection: 遍历的集合 item: 遍历出来的元素 separator: 分隔符 open: 遍历开始前拼接的SQL片段 close: 遍历结束后拼接的SQL片段 --> <delete id="deleteByIds"> delete from emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
-
单元测试
//批量删除员工 - 18, 19, 20 @Test public void testDeleteByIds(){ List<Integer> ids = Arrays.asList(18, 19, 20); empMapper.deleteByIds(ids); }
5.6.5 sql、include标签
- sql标签:定义可重用的SQL片段
- include标签:通过属性refid,指定包含的sql片段
面对以下的代码,如果要修改表名或者字段名的话,则在两个select标签中都要修改,这就导致代码的复用性差。
XML文件提供了解决方案。
<select id="list" resultType="com.itheima.pojo.Emp">
<!-- 将refid属性指定的sql片段引用进来 -->
<include refid="commonSelect"/>
<where>
<if test="name != null">
name like concat('%', #{name}, '%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
<!-- 给sql片段指定唯一标识,用于include引用 -->
<sql id="commonSelect">
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
from emp
</sql>
5.6.6 trim标签
<!--
prefix、suffix:在trim标签中内容前后添加指定内容
prefixOverrides、suffixOverrides:在trim标签中内容前后删除指定内容
-->
<select>
select * from dept
<trim prefix="where">
emp_name = #{empName}
</trim>
</select>
等同于 select * from dept where emp_name = #{empName}
5.7 mybatis的缓存机制
一级缓存
一级缓存是sqlsession级别的,就是说,使用同一个sqlsession执行sql语句,只执行一次,并将sql语句的查询结果存放在缓存中,下一次再使用相同的查询语句时就可以直接在缓存中查询。
@Test
public void testGetEmpById(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper.getEmpById(1);
System.out.println(emp1);
Emp emp2 = mapper.getEmpById(1);
System.out.println(emp2);
}
如图所示,使用同一个sqlsession,sql语句只执行了一次,第二次执行相同的语句时直接从缓存中获取。
一级缓存失效情况:
- 不同的Sqlsession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间都执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
二级缓存
二级缓存是SqlSessionFactory级别的,即通过同一个SqlSessionFactory所获取的SqlSession的SqlSession对象,查询的数据会被缓存,再通过同一个SqlSessionFactory所获取的SqlSession查询相同的数据会从缓存中获取
二级缓存开启条件:
- 在核心配置文件中,设置全局变量cacheEnabled=“true”,默认为true
- 在映射文件(CacheMapper.xml)中设置标签
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口(implements Serializable )
二级缓存失效情况:两次查询之间执行了任意增删改
@Test
public void testCache() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.getEmpById(1);
System.out.println(emp1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp2 = mapper2.getEmpById(1);
System.out.println(emp2);
}
Cache Hit Ratio 缓存命中率,不为零则说明缓存中存在。
Cache标签中可以配置属性,自行了解。
缓存的查询顺序
先查询二级缓存,没有命中,则查询一级缓存。又没命中,则查询数据库。
关闭SqlSession之后,一级缓存中的数据会写入二级缓存。
整合第三方缓存EHCache
自己查资料
mybatis逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate支持正向工程。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
主要通过下面的配置文件(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.spark</groupId>
<artifactId>mybatis_mbg</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- mybatis核心-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
<build>
<!--构建过程中用到的插件-->
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<dependencies>
<!--逆向工程核心依赖-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
新建一个配置文件,必须命名为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>
<!--
targetRuntime:执行生成的逆向工程的版本
MyBatis3:生成带条件的CRUD(奢华尊享版)
MyBatis3Simple:生成基本的CRUD(清新简洁版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 去除生成的注解 -->
<commentGenerator>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 数据库连接配置 -->
<!-- 注意xml中不支持&,用&代替 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"
userId="root" password="123456"></jdbcConnection>
<!-- 处理NUMERIC和DECIMAL类型的策略 -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 配置pojo生成的位置 -->
<javaModelGenerator targetPackage="com.spark.pojo" targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 配置sql映射文件的生成位置 -->
<sqlMapGenerator targetPackage="com.spark.mapper" targetProject="./src/main/resources">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</sqlMapGenerator>
<!-- 配置dao接口的生成位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.spark.mapper"
targetProject="./src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaClientGenerator>
<!-- 指定逆向依据的数据表 -->
<!--tableName设置为*号,可以对应所有表,此时不写domainObjectName-->
<!--domainObjectName属性指定生成出来的实体类的类名-->
<table tableName="new_emp" domainObjectName="Emp"></table>
<table tableName="new_dept" domainObjectName="Dept"></table>
</context>
</generatorConfiguration>
配置完成后,双击这个。