MyBatis代理模式开发——MyBatis基础之三

MyBatis代理模式开发

前面已经使用MyBatis完成了对Emp表的CRUD操作,都是由SqlSession调用自身方法发送SQL命令并得到结果的,实现了MyBatis的入门。

但是却存在如下缺点

  1. 不管是selectList()、selectOne()、selectMap(),都是通过SQLSession对象的API完成增删改查,都只能提供一个查询参数。如果要多个参数,需要封装到JavaBean或者Map中,并不一定永远是一个好办法。
  2. 返回值类型较固定。
  3. 只提供了映射文件,没有提供数据库操作的接口,不利于后期的维护扩展。

在MyBatis中提供了另外一种成为**Mapper代理(或称为接口绑定)**的操作方式。在实际开发中也使用该方式。下面我们就是要Mapper代理的方式来实现对Emp表的CRUD操作吧,还有完成多个参数传递、模糊查询、自增主键回填等更多的技能实现。搭建好的项目框架如图所示,相比而言,增加了接口EmployeeMapper。但是却会引起映射文件和测试类的变化。

优点:

  1. 有接口 模块之间有规范了
  2. 参数的处理多样了,接口中的方法参数列表由我们自己决定
  3. 通过代理模式由mybatis提供接口的实现类对象 我们不用写实现类了

1.使用Mapper代理方式实现查询

项目结构:注意文件路径和文件位置!!!

image-20240101111850614

准备接口和mapper映射文件

EmpMapper接口

package com.msb.mapper;

import com.msb.pojo.Emp;

import java.util.List;


public interface EmpMapper {
    /**
     * 该方法用于查询全部的员工信息
     * @return 全部员工信息封装的Emp对象的List集合
     */
    List<Emp> findAll();
}


EmpMapper.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.msb.mapper.EmpMapper">

    <!--
    1 接口的名字和Mapper映射为文件名字必须保持一致(不包含拓展名)
    2 Mapper映射文件的namespace必须是接口的全路径名
    3 sql语句的id必须是对应方法的名
    4 EmpMapper映射文件应该和接口编译之后放在同一个目录下
    -->
    <!--List<Emp> findAll();-->
    <select id="findAll" resultType="emp" >
        select * from emp
    </select>

</mapper>

在sqlMapConfig.xml核心配置文件中使用包扫描形式加载所有的映射文件

<!--加载mapper映射文件-->
<mappers>
    <!--通过类的全路径去找mapper映射文件-->
    <mapper class="com.msb.mapper.EmpMapper"/>
</mappers>

测试代码

package com.msb.test;

import com.msb.mapper.EmpMapper;
import com.msb.pojo.Dept;
import com.msb.pojo.Emp;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;


public class Test1 {

    private SqlSession sqlSession;
    @Before
    public void init(){
        SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
        InputStream resourceAsStream = null;
        try {
            resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory factory=ssfb.build(resourceAsStream) ;
        sqlSession=factory.openSession();
    }

    @Test
    public void testFindAll(){
        EmpMapper empMapper=sqlSession.getMapper(EmpMapper.class);
        List<Emp> emps = empMapper.findAll();
        emps.forEach(System.out::println);
    }


    @After
    public void release(){
        // 关闭SQLSession
        sqlSession.close();
    }

}

2.代理模式浅析

mybatis是如何通过代理模式实现查询的?

这条语句的底层使用了动态代理模式,动态创建一个EmployeeMapper的一个代理对象并赋给接口引用。所以在MyBatis中不需要显式提供Mapper接口的实现类,这也是简单的地方。

3.代理模式下的参数传递问题

下面继续使用Mapper代理方式完成更多更复杂的数据库操作,涉及多个参数传递、模糊查询,自增主键回填等内容。

3.1多参数传递

1.单个基本数据类型

2.多个基本数据类型

3.单个引用数据类型

4.map集合数据类型

5.多个引用数据类型

接口

package com.msb.mapper;

import com.msb.pojo.Emp;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;


public interface EmpMapper {
    /**
     * 该方法用于查询全部的员工信息
     * @return 全部员工信息封装的Emp对象的List集合
     */
    List<Emp> findAll();

    /**
     * 根据员工编号查询单个员工信息的方法
     * @param empno 员工编号
     * @return 如果找到了返回Emp对象,找不到返回null
     */
    Emp findByEmpno(int empno);


    /**
     * 根据员工编号和薪资下限去查询员工信息
     * @param empno 员工编号
     * @param sal 薪资下限
     * @return 多个Emp对象的List集合
     */
    List<Emp> findByDeptnoAndSal(@Param("deptno") int deptno,@Param("sal") double sal);

    List<Emp> findByDeptnoAndSal2(Map<String,Object> map);

    List<Emp> findByDeptnoAndSal3(Emp emp);

    List<Emp> findByDeptnoAndSal4(@Param("empa") Emp empa,@Param("empb") Emp empb);


}

mapper映射文件

<?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.msb.mapper.EmpMapper">

    <!--
    1 接口的名字和Mapper映射为文件名字必须保持一致(不包含拓展名)
    2 Mapper映射文件的namespace必须是接口的全路径名
    3 sql语句的id必须是对应方法的名
    4 DeptMapper映射文件应该和接口编译之后放在同一个目录下
    -->
    <!--List<Emp> findAll();-->
    <select id="findAll" resultType="emp" >
        select * from emp
    </select>


    <!--
    单个基本数据类型作为方法参数
    #{}中可以随便写,遵循见名知意
    Emp findByEmpno(int empno);
    -->
    <select id="findByEmpno" resultType="emp" >
        select * from emp where empno =#{empno}
    </select>


    <!--
     多个基本数据类型作为方法参数
     List<Emp> findByDeptnoAndSal(@Param("detpno") int deptno,@Param("sal") double sal);
     方式1 arg*     arg0 arg1 arg2 数字是索引,从0开始
     方式2 param*   param1 param2 param3 数字是编号,从1开始
     使用别名
     List<Emp> findByDeptnoAndSal(@Param("detpno") int deptno,@Param("sal") double sal);
     通过@Param注解使用别名之后,就不能再使用arg* 但是可以继续使用param*
    -->
    <select id="findByDeptnoAndSal" resultType="emp">
        <!--select * from emp where deptno =#{arg0} and sal >= #{arg1}-->
        <!-- select * from emp where deptno =#{param1} and sal >= #{param2}-->
        <!-- select * from emp where deptno =#{deptno} and sal >= #{sal}-->
    </select>


    <!--
    参数是map,{}写键的名字
    -->
    <select id="findByDeptnoAndSal2" resultType="emp" parameterType="map" >
        <!--select * from emp where deptno =#{arg0} and sal >= #{arg1}-->
        <!-- select * from emp where deptno =#{param1} and sal >= #{param2}-->
         select * from emp where deptno =#{deptno} and sal >= #{sal}
    </select>

    <!--单个引用类型,{}中写的使用对象的属性名-->
    <select id="findByDeptnoAndSal3" resultType="emp" parameterType="emp" >

        select * from emp where deptno =#{deptno} and sal >= #{sal}
    </select>

    <!--
    多个引用类型作为方法参数
     List<Emp> findByDeptnoAndSal4(@Param("empa") Emp empa,@Param("empb") Emp empb);
     如果用@Param定义了别名,那么就不能使用arg*.属性名,但是可以使用param*.属性名和别名.属性名
    -->
    <select id="findByDeptnoAndSal4" resultType="emp"  >
        <!-- select * from emp where deptno =#{arg0.deptno} and sal >= #{arg1.sal} -->
         select * from emp where deptno =#{param1.deptno} and sal >= #{param2.sal}
         <!-- select * from emp where deptno =#{empa.deptno} and sal >= #{empb.sal}-->
    </select>

</mapper>

测试代码

package com.msb.testDemo;

import com.msb.mapper.EmpMapper;
import com.msb.pojo.Emp;
import com.msb.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Test1 {
    public static void main(String[] args) {
        SqlSession sqlSession = SqlSessionUtil.getSqlSession(true);
        /*
        * 帮助我们生成一个接口下的实现类对象的
        *
        * */
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        List<Emp> emps = mapper.getAllEmp();
        for(Emp emp:emps) {
            System.out.println(emp);
        }
        // 1单个基本数据类型作为方法参数
        Emp emp = mapper.getByEmpno(7902);
        System.out.println(emp);
        // 2多个基本数据类型作为方法参数
        List<Emp> emps2 = mapper.getByDeptnoAndSal(10, 1500);
        for(Emp em:emps2) {
            System.out.println(em);
        }
        // 3单个引用类型作为方法参数
        Emp condition=new Emp();
        condition.setDeptno(10);
        condition.setSal(1500.0);
        List<Emp> emps3 = mapper.getByDeptnoAndSal2(condition);
        for(Emp em:emps3) {
            System.out.println(em);
        }

        // 4多个引用类型作为方法参数
        Emp condition1=new Emp();
        condition1.setDeptno(10);
        Emp condition2=new Emp();
        condition2.setSal(1500.0);
        List<Emp> emps4 = mapper.getByDeptnoAndSal3(condition1,condition2);
        for(Emp em:emps4) {
            System.out.println(em);
        }
        sqlSession.close();
    }
}

3.2模糊查询

在进行模糊查询时,在映射文件中可以使用concat()函数来连接参数和通配符。另外注意对于特殊字符,比如<,不能直接书写,应该使用字符实体替换。

接口

/**
 * 根据名字做模糊查询
 * @param name 模糊查询的文字
 * @return  Emp对象List集合
 */
List<Emp> findByEname( String name);

mapper映射文件

<!--List<Emp> findByEname(String name);-->
<select id="findByEname"  resultType="emp" >
    select * from emp where ename like concat('%',#{name},'%')
    <!--或者下面这种方式-->
    select * from emp where ename like '%' || #{name} || '%'
</select>

3.3自增主键回填

MySQL支持主键自增。有时候完成添加后需要立刻获取刚刚自增的主键,由下一个操作来使用。比如结算构造车后,主订单的主键确定后,需要作为后续订单明细项的外键存在。如何拿到主键呢,MyBatis提供了支持,可以非常简单的获取。

public interface DeptMapper {
    int addDept(Dept dept);
    int addDept2(Dept dept);
}
<mapper namespace="com.msb.mapper.DeptMapper">
   <!-- int addDept(Dept dept);
   useGeneratedKeys="true" 返回数据库帮我们生成的主键
   keyProperty="deptno" 生成的主键值用我们dept对象那个属性存储
   -->
    <insert id="addDept" parameterType="dept" useGeneratedKeys="true" keyProperty="deptno">
        insert into dept values(null,#{dname},#{loc})
    </insert>

    <insert id="addDept2" parameterType="dept">
        <selectKey order="AFTER" keyProperty="deptno"  resultType="int">
            select @@identity
        </selectKey>
        insert into dept values(null,#{dname},#{loc})
    </insert>
</mapper>

测试代码

SqlSession sqlSession = SqlSessionUtil.getSqlSession(true);
DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
Dept dept =new Dept(null,"AI学院","北京");
int i = mapper.addDept2(dept);
System.out.println(i);
System.out.println(dept.getDeptno());
sqlSession.close();

方式1

useGeneratedKeys:表示要使用自增的主键

keyProperty:表示把自增的主键赋给JavaBean的哪个成员变量。

以添加Dept对象为例,添加前Dept对象的deptno是空的,添加完毕后可以通过getDeptno() 获取自增的主键。

方式2

order:取值AFTER|BEFORE,表示在新增之后|之前执行中的SQL命令

keyProperty:执行select @@identity后结果填充到哪个属性中

resultType:结果类型。

技术扩展

在很多应用场景中需要新增数据后获取到新增数据的主键值,针对这样的需求一般由三种解决方式:

  • 主键自定义,用户通过UUID或时间戳等方式生成唯一主键,把这个值当做主键值。在分布式场景中应用较多。

  • 查询后通过select max(主键) from 表获取主键最大值。这种方式在多线程访问情况下可能出现问题。

  • 查询后通过select @@identity获取最新生成主键。要求这条SQL必须在insert操作之后,且数据库连接没有关闭。

3.4实现DML操作

EmpMapper接口

/**
 * 增加员工信息
 * @param emp 存储新增员工信息的Emp对象
 * @return 对数据库数据产生影响的行数
 */
int addEmp(Emp emp);

/**
 * 根据员工编号修改员工姓名的方法
 * @param empno 要修改的员工编号
 * @param ename 修改之后的新的员工名字
 * @return 对数据库数据产生影响的行数
 */
int updateEnameByEmpno(@Param("empno") int empno,@Param("ename") String ename);

/**
 * 根据员工编号删除员工信息
 * @param empno 要删除的员工编号
 * @return 对数据库数据产生影响的行数
 */
int deleteByEmpno(int empno);

EmpMapper.xml

<!--int addEmp(Emp emp);-->
<insert id="addEmp" >
    insert into emp values(DEFAULT ,#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
</insert>
<!--int updateEnameByEmpno(@Param("empno") int empno,@Param("ename") String ename);-->
<update id="updateEnameByEmpno" >
    update emp set ename =#{ename} where empno =#{empno}
</update>
<!--int deleteByEmpno(int empno);-->
<update id="deleteByEmpno" >
    delete from emp where empno =#{empno}
</update>

测试代码

package com.msb.test;

import com.msb.mapper.DeptMapper;
import com.msb.mapper.EmpMapper;
import com.msb.pojo.Dept;
import com.msb.pojo.Emp;
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 java.io.IOException;
import java.io.InputStream;
import java.util.Date;

public class Test3 {

    private SqlSession sqlSession;
    @Before
    public void init(){
        SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
        InputStream resourceAsStream = null;
        try {
            resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory factory=ssfb.build(resourceAsStream) ;
        sqlSession=factory.openSession();
    }


    @Test
    public void testAddEmp(){
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        mapper.addEmp(new Emp(null, "TOM", "SALESMAN", 7521, new Date(), 2314.0, 100.0, 10));
        sqlSession.commit();
    }

    @Test
    public void testUpdateEnameByEmpno(){
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        mapper.updateEnameByEmpno(7938, "TOM");
        sqlSession.commit();
    }

    @Test
    public void testDeletByEmpno(){
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        mapper.deleteByEmpno(7938);
        sqlSession.commit();
    }

    @After
    public void release(){
        // 关闭SQLSession
        sqlSession.close();
    }

}
  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小灰灰-58

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值