Mapper接口编程的命名习惯
Mapper接口方式的编程,需要先有一个接口。这个接口的命名一般是xxxxMapper。
比如:
User模块的Mapper,接口命名为UserMapper。
Book模块的Mapper,接口命名为BookMapper。
Mapper接口开发有四个开发规范必须遵守
1、对应的mapper配置文件的namespace属性值必须是Mapper接口的全类名。
2、Mapper接口中的方法名必须与mapper配置文件中对应的id值相同。
3、Mapper接口的方法的参数类型必须与mapper配置文件中配置的parameterType类型匹配上
4、Mapper接口的方法返回值类型必须与mapper配置文件中配置的resultType 类型匹配上
提前准备工作,准备好的项目
在mybatis-config.xml中配置你的库名我的是mybatis
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!-- 修改数据库的四个连接属性 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 配置sql语句 的那个mapper配置文件 --> <mappers> <mapper resource="com/dao/UserMapper.xml"/> </mappers> </configuration>
UserMapper代码:
public interface UserMapper { // 保存用户 public int saveUser(User user); // 更新用户 public int updateUser(User user); // 根据id删除用户 public int deleteUserById(int id); // 根据id搜索用户 public User findUserById(int id); // 搜索全部用户 public List<User> findUsers(); }
UserMapper.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.dao.UserMapper"> <!-- public Integer saveUser(User user); --> <insert id="saveUser" parameterType="com.pojo.User"> insert into t_user(`last_name`,`sex`) values(#{lastName},#{sex}) </insert> <!-- public Integer updateUser(User user); --> <update id="updateUser" parameterType="com.pojo.User"> update t_user set last_name = #{lastName} , sex = #{sex} where id = #{id} </update> <!-- public Integer deleteUserById(Integer id); --> <delete id="deleteUserById"> delete from t_user where id = #{id} </delete> <!-- public User findUserById(Integer id); --> <select id="findUserById" resultType="com.pojo.User"> select id,last_name lastName,sex from t_user where id = #{id} </select> <!-- public List<User> findUsers(); --> <select id="findUsers" resultType="com.pojo.User"> select id,last_name lastName,sex from t_user </select> </mapper>
测试类:
public class UserMapperTest { static SqlSessionFactory sqlSessionFactory; @BeforeClass public static void setUpBeforeClass() throws Exception { String url = "mybatis-config.xml"; // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream(url); // 创建SqlSessionFactory对象 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testSaveUser() { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(0, "ddddd", 1); userMapper.saveUser(user); session.commit(); System.out.println(user); } finally { session.close(); } } @Test public void testUpdateUser() { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); User user = new User(4, "eeeee", 1); userMapper.updateUser(user); session.commit(); } finally { session.close(); } } @Test public void testDeleteUserById() { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); userMapper.deleteUserById(4); session.commit(); } finally { session.close(); } } @Test public void testFindUserById() { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); System.out.println(userMapper.findUserById(1)); } finally { session.close(); } } @Test public void testFindUsers() { SqlSession session = sqlSessionFactory.openSession(); try { UserMapper userMapper = session.getMapper(UserMapper.class); System.out.println(userMapper.findUsers()); } finally { session.close(); } } }
源码解析
它是怎么工作的呢?拿测试类中查询举例
1.读取配置文件mybatis-config.xml,通过Resources.getResourceAsStream(“mybatis-config.xml”);返回一个流对象InputStream
2.通过SqlSessionFactoryBuilder().build(流对象InputStream放进来)来创建SqlSessionFactory对象
3.使用SqlSessionFactory对象打开一个session方法,sqlSessionFactory.openSession();获取SqlSession对象
4.使用SqlSession调用getMapper()方法,我们进入源码查看该方法
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
我们之前放进来的UserMapper.class对应Class type,它作为 configuration.getMapper(type, this)的参数再次传递,我们进入它的方法体内
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
它再次被当做参数和sqlSession一起传递,进入它的方法体内,现在才是重点
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
我们可以看到MapperProxyFactory这个类是Mapper接口的代理工厂,这个mapper的代理就是我们之前的UserMapper 的实现类
看看之前的代码UserMapper userMapper = session.getMapper(UserMapper.class);
接着查看源码return mapperProxyFactory.newInstance(sqlSession);通过newInstance方法它要创建UserMapper接口的实例了,反射有讲newInstance()创建实例。
mapperProxyFactory.newInstance(sqlSession)进入它的方法体内
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
newInstance(mapperProxy)进入它的方法体内
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
Proxy.newProxyInstance()方法解析:用来new一个jdk动态代理的
看看里面的参数 (mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy)
mapperInterface.getClassLoader():类加载器
new Class[] { mapperInterface }:interface com.dao.UserMapper
mapperProxy:
protected T newInstance(MapperProxy mapperProxy) {
进入MapperProxy类中
public class MapperProxy implements InvocationHandler, Serializable {
可以看到MapperProxy实现了InvocationHandler
回到之前测试类的代码:UserMapper userMapper = session.getMapper(UserMapper.class);
可以看到它已经是mapper的代理,jdk动态代理,这样可以解释为什么我们没有写接口也可以用接口里的方法,因为它通过代理给你创建了一个实现类。
继续看System.out.println(userMapper.findUsers());进入方法体内
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
为什么会进入到这个方法体内呢?userMapper.findUsers()前面证明了userMapper是jdk动态代理,jdk动态代理在执行任何方法时都会执行InvocationHandler执行里面的invoke方法
执行 final MapperMethod mapperMethod = cachedMapperMethod(method);
可以看到它已经找到了UserMapper接口中对应的方法了再看
可以看到它找到了select标签,我们接口中的方法名和我们的id值是不是对应上了,我们还可以深入的看看,select只有二种情况,一种是执行selectOne,一种是执行selectList.
当我们执行到return mapperMethod.execute(sqlSession, args);进入它的方法体内
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
通过command.getType()判断类型SELECT执行
case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break;
method.returnsVoid() 可以看到通过方法的返回值区别是执行selectOne,是执行selectList.
// 根据id搜索用户 public User findUserById(int id); // 搜索全部用户 public List<User> findUsers();
method.returnsMany() 它是返回集合,就是返回多个,可以深入的看看
public boolean returnsMany() {
return returnsMany;
}
进入方法体内
public static class MethodSignature { private final boolean returnsMany;
ctrl+f查找returnsMany
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
通过this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());可以看到它是不是集合还是数组
如果是list集合进入到result = executeForMany(sqlSession, args);进入到它的方法体内
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
可以看到它找到了SelectList
if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); }
method.returnsMap() 是不是Map
method.returnsCursor() 是不是游标
最后 result = sqlSession.selectOne(command.getName(), param);可以看到它找到了selectOne