2.XML 映射器
本章目标
- XML映射器
- 参数
本章内容
一、XML 映射器
1、创建工具类
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。更多参考
我们希望每个线程访问是各自线程中的sqlsession,这时可以考虑使用线程中的ThreadLocal来实现,常用方法有:
- set(value): 线程中放值
- get():取线程中的值
MybatisUtils
工具类具体实现
package com.woniuxy.hrms.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @author :fengsir
* @date :Created in 2024/6/13 19:53
* @description:TODO
* @modified By:
* @version: 1.0
*/
public class MybatisUtils {
private static final SqlSessionFactory sessionFactory;
//线程容器
private static final ThreadLocal<SqlSession> t1 = new ThreadLocal<>();
static {
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @Description 从线程容器中获取,没有则创建session在存放到线程容器中
* @Return org.apache.ibatis.session.SqlSession
* @Author fengSir
* @Date Create by 2024/6/13 20:07
*/
public static SqlSession getSession() {
SqlSession sqlSession = t1.get();
if (sqlSession == null) {
sqlSession = sessionFactory.openSession();
}
return sqlSession;
}
/**
* @Description 从容器线程中获取session,有则关闭,且将线程容器的session清空
* @Return void
* @Author fengSir
* @Date Create by 2024/6/13 20:09
*/
public static void closeSession() {
SqlSession sqlSession = t1.get();
if (sqlSession != null) {
sqlSession.close();
}
t1.set(null);
}
}
实现类:
try块退出时,会自动调用sqlSession.close()方法,关闭资源
能这样应用的前提是必须要实现Closeable接口才可以。
public List<Dept> queryAll() {
List<Dept> list = null;
try(SqlSession sqlSession = MybatisUtils.getSession()){
DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
list = deptMapper.queryAll();
}
return list;
}
单元测试:
@Test
public void queryAll() {
List<Dept> list = deptDao.queryAll();
list.forEach(System.out::println);
}
优化单元测试:
为了测试方便,我们就不在一一创建实现类,直接在测试类测试功能的使用,实际业务中还是需要有对应实现类操作
package com.woniuxy.hrms.mapper;
import com.woniuxy.hrms.entity.Dept;
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.After;
import org.junit.Before;
import org.junit.Test;
import javax.management.relation.Role;
import java.io.InputStream;
import java.util.List;
import static org.junit.Assert.*;
public class DeptMapperTest {
private InputStream in;
private SqlSession sqlSession;
private DeptMapper deptMapper;
@Before
public void init() throws Exception {
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
sqlSession = factory.openSession(); //里面写个true,下面每次就不用了写 sqlsession.commit(); 了
//4.使用SqlSession创建Dao接口的代理对象
deptMapper = sqlSession.getMapper(DeptMapper.class);
}
@After
public void destroy() throws Exception {
//提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
@Test
public void queryAll() {
List<Dept> depts = deptMapper.queryAll();
for (Dept dept : depts) {
System.out.println(dept);
}
}
}
2、根据id查询
单个参数: #
<select id="queryById" parameterType="int" resultType="dept">
select id,dept_name as deptName,remark from dept where id = #{id}
</select>
parameterType:将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为MyBatis 可以根据语句中实际传入的参数计算出应该使用的类型处理器(TypeHandler),默认值为未设置(unset)
这个语句名为 queryById,接受一个 int(或 Integer)类型的参数,并返回一个Dept类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:
#{id}
这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT id,dept_name as deptName,remark FROM DEPT WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
测试:
@Test
public void queryById() {
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
Dept dept = deptMapper.queryById(1);
System.out.println(dept);
}
}
2、插入操作
语法:
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
接口:
int addDept(Dept dept);
Mapper.xml:
参数是一个Dept对象:#
<insert id="addDept">
inert into dept values(#{id},#{deptName},#{remark})
</insert>
测试:
@Test
public void addDept(){
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
int i = deptMapper.addDept(new Dept(4, "网销部", "网络销售"));
session.commit();
System.out.println(i);
}
}
由于自动提交为false,所以需要要手动提交事务:session.commit();
如何得到自增的id值?
配置以下两个属性:
useGeneratedKeys=“true”:告诉 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来获取自动生成的键。
keyProperty=“id”:指定将返回的键值存储到传入对象的哪个属性中
注意传入的必须是对象,插入完成之后通过对象.getId()就可以得到自增的id值了
3、修改操作
语法:
<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
接口:
int updateDept(Dept dept);
Mapper.xml:
参数是一个Dept对象:#
<update id="updateDept">
update dept set dept_name = #{deptName},remark = #{remark} where id = #{id}
</update>
测试:
@Test
public void updateDept(){
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
int i = deptMapper.updateDept(new Dept(4, "网销部", "网络销售1"));
session.commit();
System.out.println(i);
}
}
由于自动提交为false,所以需要要手动提交事务:session.commit();
4、删除操作
语法:
<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
接口:
int deleteDept(Integer id);
参数是一个参数:#
Mapper.xml:
<delete id="deleteDept" >
delete from dept where id = #{id}
</delete>
测试:
@Test
public void deleteDept(){
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
int i = deptMapper.deleteDept(4);
session.commit();
System.out.println(i);
}
}
由于自动提交为false,所以需要要手动提交事务:session.commit();
5、模糊查询
默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。这样做更安全,更迅速,通常也是首选做法。
5.1、实现方式一
在MyBatis中进行模糊查询,通常使用LIKE
关键字,并在查询的参数周围添加通配符%
。
接口:
List<Dept> queryByNameLike(String deptName);
Mapper.xml:
<select id="queryByNameLike" parameterType="string" resultType="dept">
select id,dept_name as deptName,remark from dept where dept_name like concat('%',#{deptName},'%')
</select>
测试:
@Test
public void queryByNameLike(){
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper mapper = session.getMapper(DeptMapper.class);
List<Dept> list = mapper.queryByNameLike("部");
list.forEach(System.out::println);
}
}
5.2、实现方式二${}
实现
不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串,这时我们就需要使用${}
来实现
比如在条件搜索时,想根据不同的列名称来进行搜索,这时就可以用到$
select id, dept_name as deptName, remark from dept where ${deptName} like ‘%销售%’
List
list = deptMapper.queryByLike(“remark”);//remark为列名称
Mapper.xml:
<select id="queryByNameLike" parameterType="string" resultType="dept">
select id, dept_name as deptName, remark
from dept
where dept_name like ${deptName}
</select>
这样,MyBatis 就不会修改或转义该字符串了
测试:
在传参时,直接添加上’%内容%’
@Test
public void queryByNameLike(){
try(SqlSession session = MybatisUtils.getSession()){
DeptMapper mapper = session.getMapper(DeptMapper.class);
List<Dept> list = mapper.queryByNameLike("'%部%'");
list.forEach(System.out::println);
}
}
提示:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。更多参考
6、返回聚合函数的值
得到所有的员工个数,这时返回的是单个值
接口:
int queryCount();
Mapper.xml:
<select id="queryCount" resultType="int">
select count(*) from dept
</select>
7、返回多个聚合函数的结果
得到所有员工个数和最高工资,这里使用到了两个聚合函数来实现
接口:
Map<String,Object> queryCountAndMax();
这时可能会提示:@MapKey is required,可以不用管它,可以参考
Mapper.xml:
<select id="queryCountAndMax" resultType="map">
select count(id) as empCount,max(salary) as maxSalary from employee
</select>
测试:
@Test
public void queryCountAndMax(){
try(SqlSession session = MybatisUtils.getSession()){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Map<String, Object> map = mapper.queryCountAndMax();
System.out.println(map);
System.out.println(map.get("maxSalary"));
}
}
8、返回分组后的结果
根据部门id分组,会将每一行数据先封装为一个map,以结果集的列名作为键,对应列的值作为值, 然后将map封装到List中
接口:
List<Map<String,Object>> groupByDeptId();
这时可能会提示:@MapKey is required,可以不用管它,可以参考
Mapper.xml:
<select id="groupByDeptId" resultType="map">
select dept_id as deptId,count(id) as deptCount from employee group by dept_id
</select>
测试:
@Test
public void groupByDeptId() {
try (SqlSession session = MybatisUtils.getSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Map<String, Object>> maps = mapper.groupByDeptId();
for (Map<String, Object> map : maps) {
System.out.println(map);
map.get("deptId");
map.get("deptCount");
}
}
}
二、参数
1、单个参数
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的这个Mapper.xml说明了一个非常简单的命名参数映射,参数类型(parameterType)会被自动设置为int,这个参数可以随意命名。原始类型或简单数据类型(比如Integer 和 String)因为没有其它属性,会用它们的值来作为参数
2、对象参数
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 类型的参数对象传递到了语句中,会查找 id、username 和 password属性,然后将它们的值传入预处理语句的参数中
3、多个参数
默认为两类:
- arg0、arg1、…… argX
- param1、param1、…… paramX
接口:
List<Employee> queryByNameAndTel(String empName,String phone);
Mapper.xml:
<select id="queryByNameAndTel" resultType="employee">
select emp_name, phone, address, salary from employee where emp_name = #{empName} or phone= #{phone}
</select>
测试:
@Test
public void queryByNameAndTel(){
try(SqlSession session = MybatisUtils.getSession()){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
List<Employee> list = mapper.queryByNameAndTel("赵一","13892845500");
list.forEach(System.out::println);
}
}
抛异常信息:
Cause: org.apache.ibatis.binding.BindingException: Parameter 'empName' not found. Available parameters are [arg1, arg0, param1, param2]
优化处理:
<select id="queryByNameAndTel" resultType="dept">
select emp_name, phone, address, salary from employee where emp_name = #{arg0} or phone= #{arg1}
</select>
调整完整之后可以正常通过
4、通过@Param
注解指定多个参数名称
接口:
@Param中的参数要和xml中一致
List<Employee> queryByNameAndTel(@Param("empName") String empName, @Param("phone") String phone);
Mapper.xml:
param0可以继续使用,而arg0不能正常使用
<select id="queryByNameAndTel" resultType="employee">
select emp_name, phone, address, salary from employee where emp_name = #{empName} or phone= #{phone}
</select>
5、传入一个map类型的参数
如果在查询时有多个条件,那么我们可以把多个条件放在一个map中来作为参数传递进来
接口:
List<Employee> queryByMap(Map<String,Object> map);
Mapper.xml:
<select id="queryByMap" resultType="employee">
select emp_name, phone, address, salary from employee where emp_name = #{empName} or phone= #{phone}
</select>
{}中的内容是测试时传进来的map的key值
测试:
@Test
public void queryByMap() {
try (SqlSession session = MybatisUtils.getSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("empName","赵一");
map.put("phone","13892845500");
List<Employee> list = mapper.queryByMap(map);
list.forEach(System.out::println);
}
}
除了通过map来传递过多个参数,我们后期还可以通过定义dto的方式,把多个参数放在一个实体类中来解决
6、分页
6.1、常规方式
Mapper接口:
/**
* @Description 分页
* @param pageNum 请求的页数
* @param pageSize 每页显示条数
* @Return java.util.List<com.woniuxy.hrms.entity.Employee>
* @Author fengSir
* @Date Create by 2024/6/26 14:40
*/
List<Employee> queryByPage(@Param("pageNum") int pageNum,@Param("pageSize") int pageSize);
SqlMapper.xml文件:
<select id="queryByPage" resultType="com.woniuxy.hrms.entity.Employee">
select *
from employee
limit #{arg0},#{arg1}
</select>
测试:
public List<Employee> queryByPage(int pageNum, int pageSize) {
try(SqlSession session = MybatisUtil.getSession()){
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
int offset = (pageNum-1)*pageSize;//可以放在服务层处理
return mapper.queryByPage(offset,pageSize);
}
}
6.2、分页插件Pagehelper
pom.xml导入依赖:
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.1.0</version>
</dependency>
核心配置文件配置插件:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--配置参数helperDialect:数据库方言-->
<property name="helperDialect" value="mysql"/>
<!--配置参数reasonable:true,默认为false,表示页码越界了,也能查询出来数据-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
Service接口:
无须建立mapper层,直接通过service调用queryAll方法即可
PageInfo<Employee> queryByPageHelper(int pageNum, int pageSize);
Service实现类:
@Override
public PageInfo<Employee> queryByPageHelper(int pageNum, int pageSize) {
PageHelper.startPage(pageNum,pageSize);//通过分页插件操作
List<Employee> list = employeeMapper.queryAll();//直接调用查询全部方法
PageInfo<Employee> pageInfo = new PageInfo<>(list);//封闭成PageInfo
return pageInfo;
}
测试:
public class EmployeeServiceImplTest {
@Test
public void queryByPageHelper() {
EmployeeServiceImpl employeeService = new EmployeeServiceImpl();
PageInfo<Employee> pageInfo = employeeService.queryByPageHelper(1, 5);
System.out.println(pageInfo.getPages());//得到页数
System.out.println(pageInfo.getTotal());//得到总行数
List<Employee> list = pageInfo.getList();//得到当前数据
for (Employee employee : list) {
System.out.println(employee);
}
}
}
思维导图
本文来自博客园,作者:icui4cu,转载请注明原文链接:https://www.cnblogs.com/icui4cu/p/18832168