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_userwhere id=#{id};</delete><!--根据id更新用户--><update id="updateUser"parameterType="org.example.entity.User">update t_user setuser_name=#{name},user_password=#{password},address=#{address}whereid=#{id}</update>
</mapper>
单元测试:
@Testpublic 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);}@Testpublic 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);}@Testpublic 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">selectid,user_name as name,user_password as password,addressfrom t_userwhere id = #{id}</select>
单元测试:
@Testpublic 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">selectid,user_name as name,user_password as password,addressfrom t_user</select><!--查所有返回list<Map>--><select id="findAllUser2"resultType="java.util.Map">selectid,user_name,user_password,addressfrom t_user</select>
单元测试:
@Testpublic 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);}}@Testpublic 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">selectid,user_name as name,user_password as password,addressfrom t_user</select><!--查所有返回Map<Map>--><select id="findAllUser4"resultType="java.util.Map">selectid,user_name,user_password,addressfrom t_user</select>
单元测试:
@Testpublic 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));}}@Testpublic 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 {@Overridepublic 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 {@Testpublic 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中去处理。实现了接口和实现的分离。