参考资料(官方)
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
| |
| |
| |
| |
| |
| 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 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(); |
| |
| |
| |
| |
| |
| |
| |
| <T> T getMapper(Class<T> type); |
| |
| Connection getConnection(); |
| } |
SqlSource
| |
| |
| |
| |
| |
| |
| public interface SqlSource { |
| |
| BoundSql getBoundSql(Object parameterObject); |
| |
| } |
解释:内部封装了用户输入的SQL,这个SQL可以表现为SQL节点,也可以表现为静态SQL
1. RawSqlSource
| |
| |
| |
| |
| |
| |
| |
| 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
| |
| |
| |
| 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
| |
| |
| |
| |
| 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
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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的包装,具体还包含:
- 执行参数
- 参数的映射信息
- 附加参数
StatementHandler
对象执行时需要使用该对象
TypeHandler
处理执行SQL时的出参和出参、比较简单,不做过多解释
| |
| |
| |
| 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()); |
| |
| |
| if (Jdk.dateAndTimeApiExists) { |
| Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this); |
| } |
| |
| |
| 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标签的封装
| |
| |
| |
| 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
| |
| |
| |
| |
| |
| public interface ParameterHandler { |
| |
| Object getParameterObject(); |
| |
| void setParameters(PreparedStatement ps) |
| throws SQLException; |
| |
| } |
实现类
3、StatementHandler
| |
| |
| |
| 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
| |
| |
| |
| 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; |
| |
| } |
实现类
拦截器
| |
| |
| |
| 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 { |
| |
| |
| |
| |
| 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> |
| <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
| |
| |
| |
| 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
| |
| |
| |
| |
| |
| 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) { |
| 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
| |
| |
| |
| 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 { |
| |
| 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) { |
| |
| } catch (NoSuchMethodException e) { |
| |
| } |
| } |
| |
| |
| log = new Slf4jLoggerImpl(logger); |
| } |
| } |
十、迭代器模式
Cursor
| |
| |
| |
| |
| |
| |
| |
| public interface Cursor<T> extends Closeable, Iterable<T> { |
| |
| |
| |
| |
| boolean isOpen(); |
| |
| |
| |
| |
| |
| boolean isConsumed(); |
| |
| |
| |
| |
| |
| 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) { |
| 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优化
| |
| |
| |
| 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) { |
| |
| } |
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
菜鸟内部框架
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端