MyBatis 如何平衡易用性、灵活性和性能
MyBatis 和ORM框架介绍
- ORM:将程序中的对象存储到数据库、将数据库中的数据转化为程序中的对象。
- 在之前的讨论中提到Spring提供的JdbcTmplate简化数据库编程,让开发者只专注于业务代码的编写。驱动加载、数据库链接、Statement创建、关闭连接等都由JdbcTemplate中。
- mybatis也可以简化我们的编程,让我们专注于业务。
// 1. 定义UserDO public class UserDo { private long id; private String name; private String telephone; // 省略setter/getter方法 } // 2. 定义访问接口 public interface UserMapper { public UserDo selectById(long id); } // 3. 定义映射关系: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="cn.xzg.cd.a87.repo.mapper.UserMapper"> <select id="selectById" resultType="cn.xzg.cd.a87.repo.UserDo"> select * from user where id=#{id} </select> </mapper> // 4. 全局配置文件: mybatis.xml <?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="dev"> <environment id="dev"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="root" /> <property name="password" value="..." /> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
UserMapper.xml中体现了“约定优于配置”的设计原则。类字段和数据库表中拼写相同的字段一一映射(id)。如果不能做到一一映射也可以自己配置。
有了以上配置,我们就可利用selectById访问数据库了。在业务代码中,只需要使用预先映射好的方法即可获取想要的数据。数据库配置是在XML文件中与业务分离,也更好维护。
public class MyBatisDemo { public static void main(String[] args) throws IOException { Reader reader = Resources.getResourceAsReader("mybatis.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); UserDo userDo = userMapper.selectById(8); //... } }
如何平衡易用性、性能和灵活性?
- JdbcTemplate:更轻量级,只对JDBC做了简单封装,所以性能最好,但是缺点就是SQL和业务代码耦合,不具备ORM功能需要自定义对象和数据库数据之间的映射关系,易用性差一些对比其他两个框架。
- Hibernate对比MyBatis更加重量级,不需要使用像MyBatis那样自己编写SQL,可以自动生成SQL语句。自动生成带来的就是可能性能不如自己编写的好,而且易用性上也不如完全自定义来的好。
- 整件事情(ORM)代码逻辑是大概类似的。不同的是哪部分代码放到了哪里。强大的框架帮程序员做了很多事情但是必然带来的是更复杂的内部逻辑和更多的代码,相应的性能就会有一定的损耗。
- 所以性能与易用性存在一定的对立关系。
如何利用职责链模式和代理模式实现mybatis plugin
MyBatis Plugin功能介绍
- 在不需要修改源码的情况下拦截某些方法调用,在调用前后执行一些额外的代码逻辑。plugin主要拦截的是执行SQL过程中涉及到的方法。
@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})}) public class SqlCostTimeInterceptor implements Interceptor { private static Logger logger = LoggerFactory.getLogger(SqlCostTimeInterceptor.class); @Override public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); long startTime = System.currentTimeMillis(); StatementHandler statementHandler = (StatementHandler) target; try { return invocation.proceed(); } finally { long costTime = System.currentTimeMillis() - startTime; BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); logger.info("执行 SQL:[ {} ]执行耗时[ {} ms]", sql, costTime); } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { System.out.println("插件配置的信息:"+properties); } }
<!-- MyBatis全局配置文件:mybatis-config.xml --> <plugins> <plugin interceptor="com.xzg.cd.a88.SqlCostTimeInterceptor"> <property name="someProperty" value="100"/> </plugin> </plugins>
-
MyBatis 底层是通过Executor类来执行SQL, 这个类会创建StatementHandler(执行语句),ParameterHandler(sql占位符参数),ResultSetHandler(封装执行结果)三个对象,所以只要拦截这几个类的方法,就能满足对整个SQL执行流程的拦截。
Plugin的设计与实现
集成了MyBatis的应用在启动的时候,MyBatis会读取全局配置文件,解析出interceptor并且将他注入到configuration类的interceptorChain对象中。
public class XMLConfigBuilder extends BaseBuilder { //解析配置 private void parseConfiguration(XNode root) { try { //省略部分代码... pluginElement(root.evalNode("plugins")); //解析插件 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } //解析插件 private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //创建Interceptor类对象 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //调用Interceptor上的setProperties()方法设置properties interceptorInstance.setProperties(properties); //下面这行代码会调用InterceptorChain.addInterceptor()方法 configuration.addInterceptor(interceptorInstance); } } } } // Configuration类的addInterceptor()方法的代码如下所示 public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
Interceptor和interceptorChain
public class Invocation { private final Object target; private final Method method; private final Object[] args; // 省略构造函数和getter方法... public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); } } public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); } public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }
拦截器的触发和执行的时机
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
wrap方法实现
// 借助Java InvocationHandler实现的动态代理模式 public class Plugin implements InvocationHandler { private final Object target; private final Interceptor interceptor; private final Map<Class<?>, Set<Method>> signatureMap; private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } // wrap()静态方法,用来生成target的动态代理, // 动态代理对象=target对象+interceptor对象。 public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } // 调用target上的f()方法,会触发执行下面这个方法。 // 这个方法包含:执行interceptor的intecept()方法 + 执行target上f()方法。 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>(); for (Signature sig : sigs) { Set<Method> methods = signatureMap.get(sig.type()); if (methods == null) { methods = new HashSet<Method>(); signatureMap.put(sig.type(), methods); } try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<Class<?>>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); } }
当执行 Executor、StatementHandler、ParameterHandler、ResultSetHandler 这四个类上的某个方法的时候,MyBatis 会嵌套执行每层代理对象(Plugin 对象)上的 invoke() 方法。而 invoke() 方法会先执行代理对象中的 interceptor 的 intecept() 函数,然后再执行被代理对象上的方法。就这样,一层一层地把代理对象上的 intercept() 函数执行完之后,MyBatis 才最终执行那 4 个原始类对象上的方法。
MyBatis中用到的设计模式
SqlSessionFactoryBuilder: 为什么用建造者模式来创建工厂?
public class MyBatisDemo { public static void main(String[] args) throws IOException { Reader reader = Resources.getResourceAsReader("mybatis.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession session = sessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); UserDo userDo = userMapper.selectById(8); //... } }
看似工厂创建并不复杂,为啥还要用建造者模式?
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } public SqlSessionFactory build(Reader reader, String environment) { return build(reader, environment, null); } public SqlSessionFactory build(Reader reader, Properties properties) { return build(reader, null, properties); } public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment) { return build(inputStream, environment, null); } public SqlSessionFactory build(InputStream inputStream, Properties properties) { return build(inputStream, null, properties); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); } }
看起来并非标准的建造者模式,只是为了简化开发,将配置参数的构建隐藏。
SqlSessionFactory:什么模式?
public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }
不是标准的工厂模式。他的唯一实现如下:是为了通过不同的接口参数来创建sqlSession。
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } @Override public SqlSession openSession(boolean autoCommit) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit); } @Override public SqlSession openSession(ExecutorType execType) { return openSessionFromDataSource(execType, null, false); } @Override public SqlSession openSession(TransactionIsolationLevel level) { return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false); } @Override public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) { return openSessionFromDataSource(execType, level, false); } @Override public SqlSession openSession(ExecutorType execType, boolean autoCommit) { return openSessionFromDataSource(execType, null, autoCommit); } @Override public SqlSession openSession(Connection connection) { return openSessionFromConnection(configuration.getDefaultExecutorType(), connection); } @Override public SqlSession openSession(ExecutorType execType, Connection connection) { return openSessionFromConnection(execType, connection); } @Override public Configuration getConfiguration() { return configuration; } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) { try { boolean autoCommit; try { autoCommit = connection.getAutoCommit(); } catch (SQLException e) { // Failover to true, as most poor drivers // or databases won't support transactions autoCommit = true; } final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); final Transaction tx = transactionFactory.newTransaction(connection); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } //...省略部分代码... }
BaseExecutor: 模版模式与普通的集成的区别
sqlSession执行SQL都是委托给executor实现。Executor本身是一个一个接口,BaseExecutor是一个抽象类实现了上面的接口。而BatchExecutor、SimpleExecutor、ReuseExecutor继承了BaseExecutor。
以下可见:通过继承实现代码复用,而模板方法是抽象方法,等待子类去实现。
public abstract class BaseExecutor implements Executor { //...省略其他无关代码... @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); return doUpdate(ms, parameter); } public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException { if (closed) { throw new ExecutorException("Executor was closed."); } return doFlushStatements(isRollBack); } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } @Override public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); return doQueryCursor(ms, parameter, rowBounds, boundSql); } protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException; }
SqlNode:解释器模式解析动态SQL
<update id="update" parameterType="com.xzg.cd.a89.User" UPDATE user <trim prefix="SET" prefixOverrides=","> <if test="name != null and name != ''"> name = #{name} </if> <if test="age != null and age != ''"> , age = #{age} </if> <if test="birthday != null and birthday != ''"> , birthday = #{birthday} </if> </trim> where id = ${id} </update>
动态SQL的语法规则是MyBatis自定义的。解释器模式的应用其实就是定义一个语法规则来替换掉SQL中的动态元素。
MyBatis中的语法规则都是小单元,叫SqlNode。
ErrorContex: 如何实现线程唯一的单例模式?
public class ErrorContext { private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n"); private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>(); private ErrorContext stored; private String resource; private String activity; private String object; private String message; private String sql; private Throwable cause; private ErrorContext() { } public static ErrorContext instance() { ErrorContext context = LOCAL.get(); if (context == null) { context = new ErrorContext(); LOCAL.set(context); } return context; } }
Cache: 为什么用装饰器模式而不设计成继承子类?
灵活,代码复用,避免继承关系的代码组合爆炸。
public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; private Object eldestKey; public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } public void setSize(final int size) { keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject(Object key) { keyMap.get(key); //touch return delegate.getObject(key); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); keyMap.clear(); } @Override public ReadWriteLock getReadWriteLock() { return null; } private void cycleKeyList(Object key) { keyMap.put(key, key); if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } }
PropertyTokenizer:如何利用迭代器模式实现一个属性解释器?
// person[0].birthdate.year 会被分解为3个PropertyTokenizer对象。其中,第一个PropertyTokenizer对象的各个属性值如注释所示。 public class PropertyTokenizer implements Iterator<PropertyTokenizer> { private String name; // person private final String indexedName; // person[0] private String index; // 0 private final String children; // birthdate.year public PropertyTokenizer(String fullname) { int delim = fullname.indexOf('.'); if (delim > -1) { name = fullname.substring(0, delim); children = fullname.substring(delim + 1); } else { name = fullname; children = null; } indexedName = name; delim = name.indexOf('['); if (delim > -1) { index = name.substring(delim + 1, name.length() - 1); name = name.substring(0, delim); } } public String getName() { return name; } public String getIndex() { return index; } public String getIndexedName() { return indexedName; } public String getChildren() { return children; } @Override public boolean hasNext() { return children != null; } @Override public PropertyTokenizer next() { return new PropertyTokenizer(children); } @Override public void remove() { throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties."); } }
也不是很标准的迭代器模式因为他将解析、解析后的元素和迭代器柔和在一起。这样也有好处就是可以做到惰性解析。
Log:适配器模式适配不同的日志框架
一个例子:
import org.apache.ibatis.logging.Log; import org.apache.log4j.Level; import org.apache.log4j.Logger; public class Log4jImpl implements Log { private static final String FQCN = Log4jImpl.class.getName(); private final Logger log; public Log4jImpl(String clazz) { log = Logger.getLogger(clazz); } @Override public boolean isDebugEnabled() { return log.isDebugEnabled(); } @Override public boolean isTraceEnabled() { return log.isTraceEnabled(); } @Override public void error(String s, Throwable e) { log.log(FQCN, Level.ERROR, s, e); } @Override public void error(String s) { log.log(FQCN, Level.ERROR, s, null); } @Override public void debug(String s) { log.log(FQCN, Level.DEBUG, s, null); } @Override public void trace(String s) { log.log(FQCN, Level.TRACE, s, null); } @Override public void warn(String s) { log.log(FQCN, Level.WARN, s, null); } }