2.XML 映射器

本章目标

  1. XML映射器
  2. 参数

本章内容

一、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);
         }
     }
 }

思维导图

image

posted @ 2025-04-18 09:40  icui4cu  阅读(16)  评论(0)    收藏  举报