参考资料(官方)

Mybatis官方文档: https://mybatis.org/mybatis-3/

Mybatis-Parent : https://github.com/mybatis/parent.git

Mybatis-3 : https://github.com/mybatis/mybatis-3.git

Mybatis-Spring : https://github.com/mybatis/spring.git

Mybatis博客: https://blog.mybatis.org/

整体介绍

什么是 MyBatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录 。

Mybatis3 目前最新版本为 3.5.7 , 发布于 2021 年 4 月

Mybatis起源

iBatis 是由 Clinton Begin 于2002年发起的开源项目 , iBatis 曾是开源软件组 Apache 推出的一种轻量级的对象关系映射持久层(ORM)框架 , 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis (引用: 我们朝着与 Apache 软件基金会不同的方向发展,因此团队投票决定离开 ASF) , 至此可以理解为Mybatis3是Ibatis2 的升级版 , 2013 年 11 月 Mybatis- 3 从 google code 讲代码迁移至GitHub 。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

为什么要使用 MyBatis

  • 消除了大量 JDBC 样板代码
  • 低学习曲线
  • 支持与第三方缓存库集成
  • 拥抱 SQL
  • 更好的性能

Mybatis 优势

  • Mybatis实现了接口绑定,使用更加方便
  • 对象关系映射的改进,效率更高
  • MyBatis采用功能强大的基于OGNL的表达式来消除其他元素

版本特性

3.5.x

3.5.0 发布于2019 年 1 月

  • 支持 java.util.Optional 作为返回值
  • 摒弃了繁琐的@Results@ConstructorArgs注解 , 可以叠加 @Result@Arg
  • 新增了一个新的事务隔离级别SQL_SERVER_SNAPSHOT , 支持SQL Server的SNAPSHOT
  • 支持了Log4J 版本 2.6+
  • 支持在Sql构建器中使用 OFFSET / LIMIT 方法

3.4.x

3.4.0 发布于2016 年 4 月 , 需要JDK 1.8 的支持

  • 3.4.0 发布于2016 年 4 月
  • 增加对 Java 8 日期与时间类(JSR-310)的支持
  • 继承了Spring的事务超时时间
  • 支持 org.apache.ibatis.cursor.Cursor 作为返回值
  • Sql Provider 注解方式支持多个参数 @Param

3.3.x

3.3.0 发布于2015 年 5 月

  • 默认代理工具现在是 Javassist 并包含在 mybatis jar 中
  • 支持批量插入回写自增主键的功能

3.2.x

3.2.0 发布于 2013 年 2 月 , 需要 Jdk 1.6

  • 支持可插拔的脚本引擎
  • 支持可扩展字节码提供器和Java辅助类
  • 缓存嵌套查询
  • 改善日志
  • @SelectKey 支持多个Key属性返回

3.1.x

3.1.0 发布于 2012 年 3 月

  • 多数据库支持
  • Scala 支持
  • 多日志框架支持
  • Join语句父子关系支持
  • 性能优化

3.0.x

较早的版本

核心组件

SqlSessionFactory

/**
* Creates an {@link SqlSession} out of a connection or a DataSource
*
* @author Clinton Begin
*/
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

/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
public interface SqlSession extends Closeable { void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int update(String statement);
int delete(String statement);
void commit();
void rollback();
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration(); /**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type); Connection getConnection();
}

SqlSource

/**
* Represents the content of a mapped statement read from an XML file or an annotation.
* It creates the SQL that will be passed to the database out of the input parameter received from the user.
*
* @author Clinton Begin
*/
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }

解释:内部封装了用户输入的SQL,这个SQL可以表现为SQL节点,也可以表现为静态SQL

1. RawSqlSource

/**
* Static SqlSource. It is faster than {@link DynamicSqlSource} because mappings are
* calculated during startup.
*
* @since 3.2.0
* @author Eduardo Macarron
*/
public class RawSqlSource implements SqlSource { private final SqlSource sqlSource; public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
} public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
} private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
} @Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
} }

该对象包含已解析完成的SQL,这个SQL使用Statement对象直接可以执行。 RawSqlSource 在执行过程中比DynamicSqlSource对象快,因为在框架启动过程中就已经把需要执行的SQL解析完成了。

2. DynamicSqlSource

/**
* @author Clinton Begin
*/
public class DynamicSqlSource implements SqlSource { private final Configuration configuration;
private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
} @Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
} }

该对象包含需要动态解析的SQL,例如需要进行字符串拼接、条件判断、循环等动作才能计算好要执行的SQL语句

3. ProviderSqlSource

/**
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class ProviderSqlSource implements SqlSource { private final Configuration configuration;
private final SqlSourceBuilder sqlSourceParser;
private final Class<?> providerType;
private Method providerMethod;
private String[] providerMethodArgumentNames;
private Class<?>[] providerMethodParameterTypes;
private ProviderContext providerContext;
private Integer providerContextIndex;

@xxxProvider的实现

BoundSql

/**
* An actual SQL String got from an {@link SqlSource} after having processed any dynamic content.
* The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings
* with the additional information for each parameter (at least the property name of the input object to read
* the value from).
* </br>
* Can also have additional parameters that are created by the dynamic language (for loops, bind...).
*
* @author Clinton Begin
*/
public class BoundSql { private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<String, Object>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
} public String getSql() {
return sql;
} public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
} public Object getParameterObject() {
return parameterObject;
} public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
} public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
} public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}

可执行SQL的包装,具体还包含:

  1. 执行参数
  2. 参数的映射信息
  3. 附加参数

StatementHandler对象执行时需要使用该对象

TypeHandler

处理执行SQL时的出参和出参、比较简单,不做过多解释

/**
* @author Clinton Begin
*/
public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
	register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler()); register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // mybatis-typehandlers-jsr310
if (Jdk.dateAndTimeApiExists) {
Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
} // issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());

TypeAlias

类型别名

	registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class); registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class); registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class); registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class); registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class); registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class); registerAlias("ResultSet", ResultSet.class);

MappedStatement

一个SQL标签的封装

/**
* @author Clinton Begin
*/
public final class MappedStatement { private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}

MapperProxy

Mapper接口的代理,目前只支持JDB动态代理

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
} }

四大组件

  • Executor : MyBatis的执行器,用于执行增删改查操作
  • ParameterHandler : 处理SQL的参数对象
  • StatementHandler : 数据库的处理对象,用于执行SQL语句
  • ResultSetHandler : 处理SQL的返回结果集

1、Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

1). SimpleExecutor

2). ReuseExecutor

3). BatchExecutor

2、ParameterHandler

/**
* A parameter handler sets the parameters of the {@code PreparedStatement}
*
* @author Clinton Begin
*/
public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps)
throws SQLException; }

实现类

  • DefaultParameterHandler

3、StatementHandler

/**
* @author Clinton Begin
*/
public interface StatementHandler { Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException; void parameterize(Statement statement)
throws SQLException; void batch(Statement statement)
throws SQLException; int update(Statement statement)
throws SQLException; <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException; <E> Cursor<E> queryCursor(Statement statement)
throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }

实现类

  • SimpleStatementHandler
  • PreparedStatementHandler
  • CallableStatementHandler

4、ResultSetHandler

/**
* @author Clinton Begin
*/
public interface ResultSetHandler { <E> List<E> handleResultSets(Statement stmt) throws SQLException; <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; void handleOutputParameters(CallableStatement cs) throws SQLException; }

实现类

  • DefaultResultSetHandler

拦截器

/**
* @author Clinton Begin
*/
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }

拦截接口

  • org.apache.ibatis.session.Configuration#newExecutor
  • org.apache.ibatis.session.Configuration#newParameterHandler
  • org.apache.ibatis.session.Configuration#newResultSetHandler
  • org.apache.ibatis.session.Configuration#newStatementHandler

工具类

Wrap.java

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;
} 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;
} @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);
}
}
}

Cache

一级缓存

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;
// 一级缓存
protected PerpetualCache localCache;
}

PerpetualCache.java

public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
this.id = id;
}
}

优点

  • 设计简单
  • 默认开启

缺点

  • 命中率低(任意一个update语句都会清空缓存)
  • 容易造成缓存穿透
  • 没有淘汰策略

二级缓存

日志集成

MyBatis支持如下日志框架

  • Slf4j
  • JCL
  • log4j
  • log4j2
  • JUL
  • stdout

如何做到兼容众多框架??

public final class LogFactory {

  /**
* Marker to be used by logging implementations that support markers
*/
public static final String MARKER = "MYBATIS"; private static Constructor<? extends Log> logConstructor; static {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}
}

Spring集成Mybatis

配置SqlSessionFactoryBean

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:/org/mybatis/spring/demo/mybatis-cfg.xml"/>
<property name="mapperLocations" value="classpath:/org/mybatis/spring/demo/*Mapper.xml"/>
</bean>

配置MapperScannerConfigurer

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.demo"/>
<property name="annotationClass" value="org.springframework.stereotype.Repository"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

设计模式

一、工厂方法

DataSourceFactory

/**
* @author Clinton Begin
*/
public interface DataSourceFactory { void setProperties(Properties props); DataSource getDataSource(); } public class UnpooledDataSourceFactory implements DataSourceFactory {
protected DataSource dataSource; public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
} public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}

二、模板模式

BaseExecutor

public abstract class BaseExecutor implements Executor {

  .... 省略部分代码片段

  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;
}

三、装饰器模式

FifoCache

/**
* FIFO (first in, first out) cache decorator
*
* @author Clinton Begin
*/
public class FifoCache implements Cache { private final Cache delegate;
private final Deque<Object> keyList;
private int size; public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<Object>();
this.size = 1024;
} @Override
public String getId() {
return delegate.getId();
} @Override
public int getSize() {
return delegate.getSize();
} public void setSize(int size) {
this.size = size;
} @Override
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
} @Override
public Object getObject(Object key) {
return delegate.getObject(key);
} @Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
} @Override
public void clear() {
delegate.clear();
keyList.clear();
} @Override
public ReadWriteLock getReadWriteLock() {
return null;
} private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
} }

四、构建者模式

ResultMap.Builder

public class ResultMap {
private Configuration configuration; private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private List<ResultMapping> idResultMappings;
private List<ResultMapping> constructorResultMappings;
private List<ResultMapping> propertyResultMappings;
private Set<String> mappedColumns;
private Set<String> mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping; private ResultMap() {
} public static class Builder {
private static final Log log = LogFactory.getLog(Builder.class); private ResultMap resultMap = new ResultMap(); public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
this(configuration, id, type, resultMappings, null);
} public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
resultMap.configuration = configuration;
resultMap.id = id;
resultMap.type = type;
resultMap.resultMappings = resultMappings;
resultMap.autoMapping = autoMapping;
} public Builder discriminator(Discriminator discriminator) {
resultMap.discriminator = discriminator;
return this;
} public Class<?> type() {
return resultMap.type;
} public ResultMap build() {
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
resultMap.mappedColumns = new HashSet<String>();
resultMap.mappedProperties = new HashSet<String>();
resultMap.idResultMappings = new ArrayList<ResultMapping>();
resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
final List<String> constructorArgNames = new ArrayList<String>();
for (ResultMapping resultMapping : resultMap.resultMappings) {
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null); // .....省略部分代码 return resultMap;
} public String getId() {
return id;
} public boolean hasNestedResultMaps() {
return hasNestedResultMaps;
} public boolean hasNestedQueries() {
return hasNestedQueries;
} public Class<?> getType() {
return type;
} public List<ResultMapping> getResultMappings() {
return resultMappings;
} }

五、代理模式

ConnectionLogger

protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {

  private final Connection connection;

  @Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
}

六、策略模式

RoutingStatementHandler

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
} } @Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
} @Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}

七、组合模式

SqlNode

  public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
} List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
} NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
}

八、责任链模式

InterceptorChain

/**
* @author Clinton Begin
*/
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);
} }

九、适配器模式

Log

public class Slf4jImpl implements Log {

  private Log log;

  public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz); if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
} // Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
}

十、迭代器模式

Cursor

/**
* Cursor contract to handle fetching items lazily using an Iterator.
* Cursors are a perfect fit to handle millions of items queries that would not normally fits in memory.
* Cursor SQL queries must be ordered (resultOrdered="true") using the id columns of the resultMap.
*
* @author Guillaume Darmont / guillaume@dropinocean.com
*/
public interface Cursor<T> extends Closeable, Iterable<T> { /**
* @return true if the cursor has started to fetch items from database.
*/
boolean isOpen(); /**
*
* @return true if the cursor is fully consumed and has returned all elements matching the query.
*/
boolean isConsumed(); /**
* Get the current item index. The first item has the index 0.
* @return -1 if the first cursor item has not been retrieved. The index of the current item retrieved.
*/
int getCurrentIndex();
}

多数据源方案

mybatis-cfg.xml

  <databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>

WarehouseMapper.xml

  <select id="selectAll" resultMap="BaseResultMap" databaseId="mysql">
select
<include refid="Base_Column_List" />
from t_warehouse where id = 1;
</select>
<select id="selectAll" resultMap="BaseResultMap" databaseId="oracle">
select
<include refid="Base_Column_List" />
from t_warehouse where id = 2;
</select>

MyBatis优化

NodeHandler单例化

List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// .............
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
} NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}

MapperProxy优化

/**
* @author Lasse Voss
*/
public class MapperProxyFactory<T> { private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }

常见反例

1. 未合理使用where标签

2. 未合理使用@Param

WarehouseMapper.java

public interface WarehouseMapper {

    Warehouse selectByCodeAndName(String code, String name);

}

WarehouseMapper.xml

  <select id="selectByCodeAndName" resultMap="BaseResultMap">
select
id, gmt_create, gmt_modify, name, code, version
from t_warehouse where code = #{param1} and name = #{param2}
</select>

或者

  <select id="selectByCodeAndName" resultMap="BaseResultMap">
select
id, gmt_create, gmt_modify, name, code, version
from t_warehouse where code = #{0} and name = #{1}
</select>

3. 重复定义 Statement

4. 返回List时做判空

List<Warehouse> list = warehouseMapper.selectAll();
if (list != null) {
// do something....
}

SDK增强

名称 Star Fork
MyBatis-Plus GitHub 11.5K 3.1k
Tk-Mapper GitHub 6.5K 1.5k
Fluent Mybatis 415 39
Cdal - -

MyBatis-Plus

为简化开发而生, Mybatis 增强工具包 - 只做增强不做改变,简化CRUD操作

Tk-Mapper

Fluent Mybatis

Cdal

菜鸟内部框架

最新文章

  1. Caffe初试(二)windows下的cafee训练和测试mnist数据集
  2. C++编程小知识随手记
  3. Python3.x和Python2.x的区别
  4. Linux netstat命令详解
  5. 描述Linux shell中单引号,双引号及不加引号的简单区别(计时2分钟)
  6. mybatis实战教程(mybatis in action),mybatis入门到精通(转)
  7. CentOS下yum安装VNCserver
  8. 一. JVM发展史,运行时数据区域,四大引用
  9. MFC中消息响应机制
  10. 【自用】爬虫配置XML时拼接URL中文转Unicode问题(例如北京转成%u5317%u4EAC)
  11. 【转】Android平台下利用zxing实现二维码开发
  12. Android开源git40个App源码
  13. JSON知多少-JSON数据结构
  14. TCP的流量控制(转载)
  15. FMDB 直接将查询结果转化为字典
  16. S2S:分享出的营销机遇
  17. 记2016商大ACM省赛
  18. 【经验】JavaScript
  19. Eclipse中快捷键Ctrl + Alt + 向上箭头 或者 Ctrl + Alt + 向下箭头与Windows冲突
  20. Linux监控

热门文章

  1. CreateWindow() -- 创建普通的窗口
  2. 算法优化---素数(质数)(Java版)
  3. SourceTree 3.1.3版本跳过注册
  4. 升级vs更新,用词莫忘准确(附PHP版本升级教程)
  5. Linux扩展分区和文件系统
  6. selenium WebDriverWait
  7. 爱了,字节跳动大神最佳整理:582页Android NDK七大模块学习宝典,理论与实践
  8. 基于 Clusternet 与 OCM 打造新一代开放的多集群管理平台
  9. Setup a Simple HTTP Proxy Server
  10. Access, Modify, Change Time of Linux File