MyBatis深入理解和使用-TypeHandler
MyBatis流程图:
使用mybatis
xml
Genreator 生成代码
引入pom文件
GenreatorConfig.xml
去掉注释 suppressAllComments
执行mvn mybatis-generator.
Annotation
写POJO和一个Mapper用@Select()写SQL实现
对比
Mapper.xml
优点:跟接口分离,统一管理;复杂的语句可以不影响接口的可读性
缺点:过多的xml文件
扫描二维码关注公众号,回复: 5081282 查看本文章Annotation
接口就能看到SQL,可读性高,不需要再去找xml文件;联合查询不好维护,代码可读性差;SQL复用差
SqlSessionFactory\SqlSession\Mapper 推荐作用域
Scope
SqlSessionFactoryBuilder method
SqlSessionFactory application
SqlSession request/method (可以认为是线程级)
Mapper method
mybatis-config.xml配置相关的元素
environment 可以定义不同的环境 SqlSessionFactoryBuilder.builder(InputStream inputStream, String environment),根据不同的环境切换不同的连接
type handler 自定义typeHandler,自定义的类型会覆盖原有的typeHandler
plugin
MyBatis TypeHandler
-
每当 mybatis 在准备语句上设置参数或从结果集检索值时, 都会使用 typehandler 以适合 java 类型的方式检索该值。下表描述了默认的类型处理程序。
-
TypeHandlers列表
Type Handler | Java Types | JDBC Types |
BooleanTypeHandler |
java.lang.Boolean , boolean |
Any compatible BOOLEAN |
ByteTypeHandler |
java.lang.Byte , byte |
Any compatible NUMERIC or BYTE |
ShortTypeHandler |
java.lang.Short , short |
Any compatible NUMERIC or SHORT INTEGER |
IntegerTypeHandler |
java.lang.Integer , int |
Any compatible NUMERIC or INTEGER |
LongTypeHandler |
java.lang.Long , long |
Any compatible NUMERIC or LONG INTEGER |
FloatTypeHandler |
java.lang.Float , float |
Any compatible NUMERIC or FLOAT |
DoubleTypeHandler |
java.lang.Double , double |
Any compatible NUMERIC or DOUBLE |
BigDecimalTypeHandler |
java.math.BigDecimal |
Any compatible NUMERIC or DECIMAL |
StringTypeHandler |
java.lang.String |
CHAR , VARCHAR |
ClobReaderTypeHandler |
java.io.Reader |
- |
ClobTypeHandler |
java.lang.String |
CLOB , LONGVARCHAR |
NStringTypeHandler |
java.lang.String |
NVARCHAR , NCHAR |
NClobTypeHandler |
java.lang.String |
NCLOB |
BlobInputStreamTypeHandler |
java.io.InputStream |
- |
ByteArrayTypeHandler |
byte[] |
Any compatible byte stream type |
BlobTypeHandler |
byte[] |
BLOB , LONGVARBINARY |
DateTypeHandler |
java.util.Date |
TIMESTAMP |
DateOnlyTypeHandler |
java.util.Date |
DATE |
TimeOnlyTypeHandler |
java.util.Date |
TIME |
SqlTimestampTypeHandler |
java.sql.Timestamp |
TIMESTAMP |
SqlDateTypeHandler |
java.sql.Date |
DATE |
SqlTimeTypeHandler |
java.sql.Time |
TIME |
ObjectTypeHandler |
Any | OTHER , or unspecified |
EnumTypeHandler |
Enumeration Type | VARCHAR any string compatible type, as the code is stored (not index). |
EnumOrdinalTypeHandler |
Enumeration Type | Any compatible NUMERIC or DOUBLE , as the position is stored (not the code itself). |
InstantTypeHandler |
java.time.Instant |
TIMESTAMP |
LocalDateTimeTypeHandler |
java.time.LocalDateTime |
TIMESTAMP |
LocalDateTypeHandler |
java.time.LocalDate |
DATE |
LocalTimeTypeHandler |
java.time.LocalTime |
TIME |
OffsetDateTimeTypeHandler |
java.time.OffsetDateTime |
TIMESTAMP |
OffsetTimeTypeHandler |
java.time.OffsetTime |
TIME |
ZonedDateTimeTypeHandler |
java.time.ZonedDateTime |
TIMESTAMP |
YearTypeHandler |
java.time.Year |
INTEGER |
MonthTypeHandler |
java.time.Month |
INTEGER |
YearMonthTypeHandler |
java.time.YearMonth |
VARCHAR or LONGVARCHAR |
JapaneseDateTypeHandler |
java.time.chrono.JapaneseDate |
DATE |
-
MyBatis TypeHandler (xml定义 或者 java config注入 或者@Annotation)
<typeHandlers>
<typeHandler handler="com.sxs.mybatis.demo.type.MytypeHander"/>
</typeHandlers>
-
示例Demo
// 创建自己的Handler,并定义MappedJdbcTypes @MappedJdbcTypes(JdbcType.VARCHAR) public class MytypeHander extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter + " i love you"); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName) + " 尚先生"; } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex) + " 尚先生"; } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex) + " 尚先生"; } }
// 创建自己的Handler,并定义MappedJdbcTypes public static SqlSession getSqlSession() throws FileNotFoundException { //配置文件 InputStream configFile = new FileInputStream( "D:\\MyWorkSpace\\mybatis\\src\\main\\java\\com\\sxs\\mybatis\\demo\\mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile); //加载配置文件得到SqlSessionFactory return sqlSessionFactory.openSession(); } public static void main(String[] args) throws FileNotFoundException { SqlSession sqlSession = getSqlSession(); TestMapper testMapper = sqlSession.getMapper(TestMapper.class); long start = System.currentTimeMillis(); Test test = testMapper.selectByPrimaryKey(id); System.out.println("cost:" + (System.currentTimeMillis() - start)); } // 添加TypeHandler之前查询运行结果 cost:327 Test(id=2, nums=200, name=魅族) // 添加TypeHandler之后查询运行结果 cost:409 Test(id=2, nums=200, name=魅族 尚先生
-
MyBatis Plugin
-
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query)
-
为了减少数据库的压力,防止大量的请求直接打在DB上,造成DB挂掉,或性能急剧下降而引发的一系列问题;
-
为了减少资源的浪费,MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
-
对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
-
判断从Cache中根据特定的key值取的数据数据是否为空,即是结果是否存在;
-
实现的本质:调用JDBC的时候,传入的SQL语句要完全相同,传递给JDBC的参数值也要完全相同。
-
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
-
1541993073636
-
close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
-
clearCache()方法,会清空PerpetualCache对象中的数据,但是该对象仍可使用;
-
update()、delete()、insert()方法,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
-
Annotation
-
xml文件开启
-
自动提交设置为false,正常提交,异常时回滚。
-
SqlSession\Mapper 生命周期管理
-
示例Demo
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private String name; @Override public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String str = (String) properties.get("someProperty"); this.name = str; System.out.println(str); } }
事务管理
JDBC事务
public static boolean insert(Test test) throws SQLException { Connection connection = null; PreparedStatement preparedStatement = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "test", "123456"); // 自动提交设置为false connection.setAutoCommit(false); preparedStatement = connection.prepareStatement("INSERT INTO test VALUES (?,?,?)"); if (null != test.getId()) { preparedStatement.setInt(1, test.getId()); } else { preparedStatement.setNull(1, INTEGER); } preparedStatement.setInt(2, test.getNums()); preparedStatement.setString(3, test.getName()); preparedStatement.execute(); connection.commit(); return true; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { try { if (null != connection) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return true; }
MyBatis自带事务
SqlSession --> Exxecutor --> Transaction --> Connection
之后就是JDBC的处理逻辑
Mybatis-Spring-x.x.x.jar
mapper是怎么注入进来的
XML 或者 java Config- ->
MapperScannerConfigurer
-->BeanDefinitionRegistryPostProcessor
-->BeanFactoryPostProcessor
告诉Spring去加载MyBatisConfigpublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; }
Annotation -->
MapperScannerRegistrar
-->ImportBeanDefinitionRegistrar
告诉Spring去加载MyBatisConfigpublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1 if (resourceLoader != null) { scanner.setResourceLoader(resourceLoader); } Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass)); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>(); for (String pkg : annoAttrs.getStringArray("value")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } } for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) { basePackages.add(ClassUtils.getPackageName(clazz)); } scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); }
public interface ImportBeanDefinitionRegistrar { void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2); }
sqlsession
的生命周期在没有@Transaction
的情况下是request/method级别的;在有@Transaction
的情况下是
@Transaction
范围内的。Mapper
在没有spring的时候是Method
级别的,在有的时候是全局的Singleton
。// set definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(this.mapperFactoryBean.getClass());
// get Configuration configuration = getSqlSession().getConfiguration();
分页查询
逻辑分页
org.apache.ibatis.executor.resultset.DefaultResultSetHandler # handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); // 判断要跳到响应的offset对应的位置 skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } // 具体判断逻辑 private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) { rs.absolute(rowBounds.getOffset()); } } else { //具体实现 for (int i = 0; i < rowBounds.getOffset(); i++) { rs.next(); } } }
物理分页
拼SQL
select * from table limit 1000, 10;
插件
mybatis PageHelper https://github.com/pagehelper/Mybatis-PageHelper/tree/master/src/main/java/com/github/pagehelper
自定义拦截器实现分页功能,拦截点如上述拦截器描述
-
mybatis 允许您在映射语句执行过程中的某些点拦截对调用。默认情况下, mybatis 允许插件拦截方法调用:
-
MyBatis深入理解和使用-MyBatis缓存体系:https://blog.csdn.net/shang_xs/article/details/86656353
MyBatis深入理解和使用-MyBatis事务管理:https://blog.csdn.net/shang_xs/article/details/86656649