Mybatis操作数据库的两种方式:Mapper代理模式

1.Mapper代理模式的特点

程序员没有写接口的子实现——直接获取数据库的数据

因为Mybatis定义了一套规则,对方法进行了实现,程序员只要遵循这套方法就可以直接使用

2.如何实现Mapper代理模式

步骤:

1.创建一个dao接口,在接口中写增删改查的方法

2.创建一个子清单文件,且子清单文件中的命名空间必须namespace="包名.接口名"

3.在子清单文件中:select\insert\update\delete 节点id为接口中的方法名称

        方法的参数对应parameterType,返回值对应resultType

4.在总清单文件引入子清单文件

5.在需要的地方:接口类型 jdk动态代理对象=sqlSqssion.getMapper(接口类型.class)

       返回类型 返回类型对象 jdk动态代理对象.调用目标方法();

程序员没有写接口子实现,就能获得数据库数据

configruation.xml:

 设置类别名,方便书写:

    <typeAliases>
        <!--设置别名-->
        <typeAlias type="org.example.entity.User" alias="User"/>
    </typeAliases>

注意添加子清单文件 

 <mapper resource="mapper/userMapper.xml"/>

增删改实现 

xxMapper.java

    public int addUser(User user);
    public int deleteUser(Integer id);
    public int updateUser(User user);

userMapper.xml

<mapper namespace="org.example.dao.UserMapper">
    <!--插入用户-->
    <insert id="addUser"
            parameterType="User">
        insert into t_user(
        user_name,
        user_password,
        address)
        values(
        #{name},
        #{password},
        #{address}
        );
    </insert>
    <!--删除用户-->
    <delete id="deleteUser"
            parameterType="java.lang.Integer">
        delete from t_user
        where id=#{id};
    </delete>
    <!--根据id更新用户-->
    <update id="updateUser"
            parameterType="org.example.entity.User">
        update t_user set
        user_name=#{name},
        user_password=#{password},
        address=#{address}
        where
        id=#{id}
    </update>
</mapper>

单元测试:


    @Test
    public void addUser() {
        //假数据
        User user=new User();
        user.setName("add");
        user.setPassword("654321");
        user.setAddress("测试用例|mapper.addUser()");
        Integer rowAffect=0;
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            rowAffect= userMapper.addUser(user);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        System.out.println(rowAffect);
    }

    @Test
    public void deleteUser() {
        Integer rowAffect=0;
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            rowAffect= userMapper.deleteUser(9);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        System.out.println(rowAffect);
    }

    @Test
    public void updateUser() {
        //假数据
        User user=new User();
        user.setId(9);
        user.setName("update");
        user.setPassword("654321");
        user.setAddress("测试用例|mapper.updateUser()");
        Integer rowAffect=0;
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            rowAffect= userMapper.updateUser(user);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        System.out.println(rowAffect);
    }

 效果展示:

addUser

updateUser 

 deleteUser

总结:

mybatis 的 mapper代理模式和原生api相比,可以不写接口的具体实现类。

mapper模式是纯接口调用,因为有接口的存在,可以使用jdk动态代理为其动态生成实现类。

jdk生成的实现类和接口之间是实现和被实现的关系,

jdk生成的实现类是实现类,也是代理类,其创建动态代理对象,通过代理对象.目标方法的方式,即invacationHandler调用invoke(),来进行实现。

查询:根据id获取用户信息

xxMapper.java

  public User findUserById(Integer id);

userMapper.xml

    <!--根据id获得用户信息-->
    <select id="findUserById"
            resultType="org.example.entity.User"
            parameterType="java.lang.Integer">
        select
        id,
        user_name as name,
        user_password as password,
        address
        from t_user
        where id = #{id}
    </select>

单元测试:

    @Test
    public void findUserById() {
        User user=null;
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            user= userMapper.findUserById(1);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        System.out.println(user);
    }

结果展示:

查询:根查所有用户List

xxMapper.java

    //返回list结构
    public List<User> findAllUser1();
    public List<Map<String,Object>> findAllUser2();

userMapper.xml

<!--查所有返回list<User>-->
    <select id="findAllUser1"
            resultType="User"
    >
        select
        id,
        user_name as name,
        user_password as password,
        address
        from t_user
    </select>

    <!--查所有返回list<Map>-->
    <select id="findAllUser2"
            resultType="java.util.Map"
    >
        select
        id,
        user_name,
        user_password,
        address
        from t_user
    </select>

单元测试:


    @Test
    public void findAllUser1() {
        List<User> userList=new ArrayList<User>();
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            userList= userMapper.findAllUser1();
            sqlSession.commit();

        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        for(User user:userList){
            System.out.println(user);
        }
    }

    @Test
    public void findAllUser2() {
        List<Map<String,Object>> userList=new ArrayList<Map<String, Object>>();
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            userList= userMapper.findAllUser2();
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        for(Map<String,Object> user:userList){
            System.out.println(user);
        }
    }

结果展示:

 

查询:根查所有用户Map

xxMapper.java

    //返回map结构
    @MapKey("id")
    public Map<Integer,User> findAllUser3();
    @MapKey("id")
    public Map<Integer,Map<String,Object>> findAllUser4();

userMapper.xml

    <!--查所有返回Map<User>-->
    <select id="findAllUser3"
            resultType="User"
    >
        select
        id,
        user_name as name,
        user_password as password,
        address
        from t_user
    </select>

    <!--查所有返回Map<Map>-->
    <select id="findAllUser4"
            resultType="java.util.Map"
    >
        select
        id,
        user_name,
        user_password,
        address
        from t_user
    </select>

单元测试:

    @Test
    public void findAllUser3() {
        Map<Integer,User> userMap= new HashMap<Integer, User>();
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            userMap= userMapper.findAllUser3();
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        for(Integer key:userMap.keySet()){
            System.out.println(key+" "+userMap.get(key));
        }
    }

    @Test
    public void findAllUser4() {
        Map<Integer,Map<String,Object>> userMap= new HashMap<Integer, Map<String,Object>>();
        SqlSession sqlSession=null;
        try{
            sqlSession= MybatisUtil.getSession();
            //接口类型, jdk动态代理对象=sqlSession.getMapper();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            userMap= userMapper.findAllUser4();
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
            sqlSession.rollback();
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
        for(Integer key:userMap.keySet()){
            System.out.println(key+" "+userMap.get(key));
        }
    }

结果展示:

3.JDKProxy模拟Mapper代理实现

Mybatis的Mapper代理模式,可以通过接口使用jdk的代理机制实现自动代理。(jdk的动态代理是基于接口的,而此处的xxMapper接口即为被代理的对象接口)

        本质上xxMapper并没有实现类,jdk的动态代理也没有对所谓的老方法进行代理。

程序员不需要写接口的具体实现,只要根据规则定义好接口,即可使用相关功能。

我们使用JDKProxy类模拟Mybatis实现getMapper()的方法,理解其中的内部实现。

package org.example.proxy;

import org.example.proxy.handler.MapperHandler;

import java.lang.reflect.Proxy;

public class JDKProxy {

    /**
     * 根据接口获取代理对象
     * @param clazz
     * @return
     */
    public static Object getMapper(Class clazz){
        Object proxyObject=null;
        /**
         * 参数一: 类加载器
         * 参数二: 接口数组
         * 参数三: InvocationHandler 接口的回调
         */
        proxyObject= Proxy.newProxyInstance(
             clazz.getClassLoader(),
             new Class[]{clazz},
             new MapperHandler()
        );
        return proxyObject;
    }

}

这里虽然使用了动态代理,但是并不是动态代理的常规用法,这里只有代理对象的接口,且该接口没有任何实现。动态代理在这里的目的并不是调用老方法做切面设计,而是获取方法信息:参数列表、返回值等,进行判断,根据方法选择调用mybatis的原生api。所以Mapper代理模式底层调用的还是Mybatis的原生api,  其只是通过动态代理实现了一个默认实现版本,来较少程序员的代码量。

关于JDK的第三个参数 InvocationHandler 接口的回调,我们构建实现类MapperHandler,其实现InvocationHandler接口。具体来说就是:

package org.example.proxy.handler;

import org.example.entity.User;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MapperHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object returnValue=null;
        System.out.println("由Mybatis根据若干信息选择执行对应的api");
        /**
         * mybatis 根据method能得到目标方法的返回值,方法名,参数类型
         * if判断返回的数据为一个值,则调用mybatis的原生api  selectOne方法
         * if判断返回的数据为多个值时,看返回类型是否为List,则调用mybatis的原生 selectList()方法
         * if判断返回的数据为多个值时,看返回类型是否为Map,则调用mybatis的原生 selectMap()方法
         * if判断为insert/delete/update时,调用原生api  insert()  delete()  update()
         */
        //eg:执行selectOne()
        User user=new User();
        user.setId(1);
        user.setName("aaa");
        user.setPassword("123456");
        user.setAddress("诺亚方舟");
        returnValue=user;
        return returnValue;
    }
}

 使用单元测试来对方法进行测试:

package org.example.proxy;

import org.example.dao.UserMapper;
import org.example.entity.User;
import org.junit.Test;

import static org.junit.Assert.*;

public class JDKProxyTest {

    @Test
    public void getMapper() {
        //接口类型 jdk动态代理对象=sqlSession.getMapper(接口类型.class);
        UserMapper userMapper=(UserMapper) JDKProxy.getMapper(UserMapper.class);
        User user=userMapper.findUserById(1);
        System.out.println(user);
    }
}

值得注意的是,getMapper中我们并没有写具体的实现逻辑,因为测试的是findUserById()方法,我们在getMapper()中返回了一个假数据,用来做测试。而Mybatis则根据逻辑写了具体的实现。 

4.Mybatis 中getMapper的实现

重点:模拟getMapper的实现


    public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch (this.command.getType()) {
            case INSERT:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

基本流程:getMapper()接口——>创建Proxy对象——>execute()实现具体实现   

特别的关于select()方法

其中method.hasResultHandler()来实现自定义的方法处理

还可以匹配不同的返回类型,其中游标模式Cursor不怎么用了

5.总结

接口定义:userDao 和UserMapper一样的写法不同,用来定义方法接口

        其可以自己写实现类也可以通过mapper() 方式来操作数据

总清单文件:

        数据库配置

        子清单文件

                模板统一

                原生写法的子清单文件 namespace和增删改查节点信息任意配置

                mapper写法的子清单 namespace="包名.接口"

                        接口方法名和节点id一致

                        接口方法参数和节点parameterType一样

                        接口的返回值跟节点resultType一样

mybatis自己的api解析xml来构建sqlSessionFactory,用于生产SqlSession

使用sqlSession来做增删改查

mapper接口方法底层还是原生api  api结合jdk动态代理

原生api

  • insert() delete() update()
  • selectOne()  selectList() selectMap()
  • select() 自定义返回的数据结构(策略+回调)
  • 返回结果类型丰富

6.设计模式:

代理模式:MapperProxy实现jdk动态代理

7.补充

什么是面向接口编程?

如select面向接口、jdbc面向接口和spring面向接口都有相关的面向对象接口编程,其目的是实现解耦操作

  • 面向接口编程是一种编程范式,它强调的是在设计软件应用时,应该先定义接口,然后再实现接口。这种方式有很多优点,包括提高代码的可读性、可维护性和可扩展性,以及降低代码之间的耦合度。
  • 接口是一种契约,它定义了一组方法,这些方法应该在实现接口的类中实现。接口本身并不包含任何实现细节,它只是定义了一种规范,规定了实现接口的类应该做什么,而不是怎么做。

摘自:《17.Spring 面向接口编程 - 知乎

select()也是面向接口编程提供select接口但没有实现,其具体的实现在handler中去处理。实现了接口和实现的分离。


 

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值