MyBatis深入理解和使用-TypeHandler

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.Booleanboolean Any compatible BOOLEAN
ByteTypeHandler java.lang.Bytebyte Any compatible NUMERIC or BYTE
ShortTypeHandler java.lang.Shortshort Any compatible NUMERIC or SHORT INTEGER
IntegerTypeHandler java.lang.Integerint Any compatible NUMERIC or INTEGER
LongTypeHandler java.lang.Longlong Any compatible NUMERIC or LONG INTEGER
FloatTypeHandler java.lang.Floatfloat Any compatible NUMERIC or FLOAT
DoubleTypeHandler java.lang.Doubledouble Any compatible NUMERIC or DOUBLE
BigDecimalTypeHandler java.math.BigDecimal Any compatible NUMERIC or DECIMAL
StringTypeHandler java.lang.String CHARVARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOBLONGVARCHAR
NStringTypeHandler java.lang.String NVARCHARNCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] Any compatible byte stream type
BlobTypeHandler byte[] BLOBLONGVARBINARY
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去加载MyBatisConfig

      public 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去加载MyBatisConfig

      public 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

猜你喜欢

转载自blog.csdn.net/shang_xs/article/details/86656173
今日推荐