MyBatis中Mapper接口映射到数据库原理分析

来源:互联网 发布:金隅悦城丽悦园网络 编辑:程序博客网 时间:2024/06/07 01:34

刚学习MyBatis,对于用@Mapper注解的接口能够完成POJO对象到数据库记录映射百般疑惑,一开始很纳闷为什么不需要定义Mapper接口的实现类就能完成这个过程。例如以下代码段中只定义了UserDAO这个接口,但是并不影响POJO对象到数据库的映射。查阅了相关的资料,稍有解惑。

@Mapperpublic interface UserDAO {    String TABLE_NAME = "user";    String INSERT_FIELDS = "name, password, salt, head_url";    String SELECT_FIELDS = "id, name, password, salt, head_url";    @Insert({"insert into", TABLE_NAME, "(", INSERT_FIELDS, ") values(#{name}, #{password}, #{salt}, #{headUrl})"})    int addUser(User user);}public class InitDBTest {@AutowiredUserDAO userDAO;public void contextLoads() {User user = new User();user.setName("user1");user.setPassword("");user.setSalt("");user.setHeadUrl("xxx.png");userDAO.addUser(user);}}

1. MapperRegistry


程序启动之初MyBatis就创建了这个类的一个实例,它有一个HashMap类型的属性用于存储每个Mapper接口(key)和相应的MapperProxyFactory(value);另外有两个重要的方法getMapper()和addMapper(),分别用于获取和注册Mapper接口到这个HashMap中。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);        if(mapperProxyFactory == null) {            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");        } else {            try {                return mapperProxyFactory.newInstance(sqlSession);//通过Mapper接口的type返回一个相应的mapperProxyFactory的一个实例            } catch (Exception var5) {                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);            }        }    }public <T> void addMapper(Class<T> type) {        if(type.isInterface()) {            if(this.hasMapper(type)) {                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");            }            boolean loadCompleted = false;            try {                this.knownMappers.put(type, new MapperProxyFactory(type));//将这个Mapper接口“注册”                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);                parser.parse();                loadCompleted = true;            } finally {                if(!loadCompleted) {                    this.knownMappers.remove(type);                }            }        }    }

2. MapperProxyFactory


正如它的名字mapper代理工厂“,这个类是一个生产mapper代理对象的工厂,而mapper代理对象负责代替mapper接口完成POJO到数据库的映射。在MapperProxyFactory中的两个重要方法如下,用于创建mapper代理类的一个实例。

protected T newInstance(MapperProxy<T> mapperProxy) {//用了java的动态代理,获得真正的代理对象        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);    }public T newInstance(SqlSession sqlSession) {        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);        return this.newInstance(mapperProxy);    }

3. MapperProxy


通过调用特定的mapper代理对象的invoke()方法,实现到数据库的映射,实际上invoke()中又调用了MapperMethod中的execute方法,execute方法主要是用到sqlSession的insert、update、select、delete等,这部分就是我们都很熟悉的了。

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 var5) {                throw ExceptionUtil.unwrapThrowable(var5);            }        } else {            MapperMethod mapperMethod = this.cachedMapperMethod(method);            return mapperMethod.execute(this.sqlSession, args);        }    }

4. MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {        Object param;        Object result;        if(SqlCommandType.INSERT == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));        } else if(SqlCommandType.UPDATE == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));        } else if(SqlCommandType.DELETE == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));        } else if(SqlCommandType.SELECT == this.command.getType()) {            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);            }        } else {            if(SqlCommandType.FLUSH != this.command.getType()) {                throw new BindingException("Unknown execution method for: " + this.command.getName());            }

回到最上面的demo,userDAO属性被注解为@Autowired,由spring完成bean的自动装配,其中spring会去获取一个MyBatis的mapperProxy代理对象作为这个bean,它是一个真正的对象而不是一个Interface类型,由这个代理人完成到数据库的映射。