MyBatis 笔记

配置

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>

application.yml

spring:
    datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      dbcp2:
        driver-class-name: com.mysql.cj.jdbc.Driver
        password: ***
        url: jdbc:mysql://127.0.0.1:3306/pzx?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
        username: app
        max-total: 1024

Config文件


@Component
@ConfigurationProperties("spring.datasource.dbcp2")
class MySqlConfig {

    var driverClassName = "com.mysql.jdbc.Driver"
    var username = ""
    var password = ""
    var url = ""
    var maxTotal = 0;
    var maxIdel = 0;
    var maxWaitMillis = 0L

    @Bean
    fun dataSource(): BasicDataSource {
        println("BasicDataSource inited: ${url}")
        val dataSource = BasicDataSource()
        dataSource.driverClassName = driverClassName
        dataSource.url = url
        dataSource.username = username
        dataSource.password = password
        dataSource.maxTotal = maxTotal
        dataSource.maxIdle = maxIdel
        dataSource.maxWaitMillis = maxWaitMillis
        dataSource.setValidationQuery("SELECT 1")
        dataSource.testOnBorrow = true
        return dataSource
    }

}


@Component
@AutoConfigureAfter(MySqlConfig::class)
class MyBatisSessionConfig {
    @Bean
    fun mapperScannerConfigurer(): MapperScannerConfigurer {
        val mapperScannerConfigurer = MapperScannerConfigurer()
        //获取之前注入的beanName为sqlSessionFactory的对象
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory")
        //指定xml配置文件的路径
        mapperScannerConfigurer.setBasePackage("pzx.db.mybatis.mapper")
        return mapperScannerConfigurer
    }
}


@Configuration
//加上这个注解,使得支持事务
@EnableTransactionManagement
class MyBatisConfig : TransactionManagementConfigurer {

    @Autowired
    private var dataSource: DataSource? = null

    override fun annotationDrivenTransactionManager(): PlatformTransactionManager {
        return DataSourceTransactionManager(dataSource!!)
    }

    @Bean(name = arrayOf("sqlSessionFactory"))
    fun sqlSessionFactoryBean(): SqlSessionFactory? {
        val bean = SqlSessionFactoryBean()
        bean.setDataSource(dataSource)

        try {
            return bean.`object`
        } catch (e: Exception) {
            e.printStackTrace()
            throw RuntimeException(e)
        }

    }

    @Bean
    fun sqlSessionTemplate(sqlSessionFactory: SqlSessionFactory): SqlSessionTemplate {
        return SqlSessionTemplate(sqlSessionFactory)
    }
}

Mapper文件

@Mapper
interface CityMapper {
    @Select("select * from s_city where code = #{code}")
    @Results(value = arrayOf(Result(column = "password", property = "password")))
    fun findByCode(@Param("code") code: String): SysCity?
}

调用

    @Autowired
    lateinit var ds : CityMapper

    @GetMapping("/testMySql")
    fun testMySql(request: HttpServletRequest): String {
        var e = ds.findByCode("110")

        return request.UserId;
    }

源码跟踪

关于缓存参考:
https://www.jianshu.com/p/c553169c5921

而我想要的缓存是:

  1. 拦截 query , update
  2. query 时,自定义 cacheKey , 及缓存策略。
  3. update 时,清空缓存。
    参考: https://blog.csdn.net/mingjia1987/article/details/79424272

依次执行:

  • MapperMethod.execute -> sqlSession.selectOne

  • SqlSessionTemplate.selectOne

  • DefaultSqlSession.selectOne

  • DefaultSqlSession.selectList ->
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

    到这里, Configuration 出现了。

  • CachingExecutor.query ->
    createCacheKey
    query

    出现在Sql及CacheKey: 383339099:1114119213:pzx.db.mybatis.mapper.CityMapper.findByCode:0:2147483647:select * from s_city where code = ?:110:SqlSessionFactoryBean
    分为以下部分: hashcode:checksum:各个部分。 前面的 hashcode:checksum 可以表示唯一了, 添加后面的部分, 是为了描述元数据。个人感觉后面部分可以简化为: 排序关联表(主键的唯一值) 的方式。唯一值仅在关联表是一个,且根据主键查询的情况。

  • CachingExecutor.query ->
    MappedStatement.getCache
    SimpleExecutor.query

    可惜 MappedStatement.getCache 返回了空。没走缓存。

  • BaseExecutor.query ->
    PerpetualCache: localCache .getObject
    else queryFromDatabase

    最后执行:
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    // issue #482
    clearLocalCache();
    }

    默认配置 configuration.localCacheScope = LocalCacheScope.SESSION ,简单来说,是一次连接一个会话,每个会话有独立的缓存数据 , 这里也使用了缓存。也返回了空,因为第二次刷新页面,是一个新的会话。

    localCache .getObject 是网上说的一级缓存, 也就是说,上面的 MappedStatement.getCache 是二级缓存。 逻辑:
    先从二级缓存查,查不到再从一级缓存查。
    这是有道理的: 因为一级缓存无法感知外部数据变化,可能有脏数据。那么如果外部数据由于 update 等操作,把缓存删掉,再从一级缓存查,就查出脏数据了。一级缓存的破坏是怎样进行的? 应该在外部 update 等 更新操作事件后, 把所有关联的一级缓存清空。
    应该在 update , delete, insert 操作之后, 自动清空所有的相关表的一级缓存, 待验证。

  • BaseExecutor.queryFromDatabase ->

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  • SimpleExecutor.doQuery

  • StatementHandler.query

  • RoutingStatementHandler.query

  • PreparedStatementHandler.query ->
    PreparedStatement.execute
    DefaultResultSetHandler.handleResultSets

    PreparedStatement.execute 是真正的执行。
    DefaultResultSetHandler.handleResultSets 应该是缓存数据的。

  • DefaultResultSetHandler.handleResultSets ->
    handleResultSet ->
    storeObject -> callResultHandler -> 数据保存在 : DefaultResultContext.resultObject -> DefaultResultHandler.handleResult 数据也存在了 DefaultResultHandler.list 中。

生成器

在项目根目录,建一个 lib 文件夹, 里面放:

  • mysql-connector-java-6.0.6.jar
  • mybatis-generator-core-1.3.6.jar

.\src\main\resources\generator\generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
  <classPathEntry location=".\lib\mysql-connector-java-6.0.6.jar" />
  <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">

    <!--<plugin type="tk.mybatis.mapper.generator.MapperPlugin">-->
      <!--<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>-->
      <!--&lt;!&ndash; caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true &ndash;&gt;-->
      <!--<property name="caseSensitive" value="true"/>-->
    <!--</plugin>-->


    <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                    connectionURL="jdbc:mysql://192.168.3.1:3306/xy_broker"
                    userId="root"
                    password="">
    </jdbcConnection>

    <javaModelGenerator targetPackage="com.xyauto.interact.broker.server.entity" targetProject=".\src\main\java"/>
    <sqlMapGenerator targetPackage="mapper"  targetProject=".\src\main\resources"/>

    <!--<javaClientGenerator targetPackage="com.xyauto.interact.broker.server.dao" targetProject="D:\xycode-git\broker\broker-server\src\main\java" type="XMLMAPPER" />-->

    <table tableName="statistics_clue_broker_day" >
    </table>
  </context>
</generatorConfiguration>

Java -jar .\lib\mybatis-generator-core-1.3.6.jar -configfile .\src\main\resources\generator\generatorConfig.xml -overwrite

日志

    @Bean
    fun abc() : SqlSessionFactory {
        SqlSessionFactory fac = factory.getObject();
        fac.getConfiguration().setLogImpl(MyBatisLog.class);
        return fac;
    }


package com.xingyuanauto.api.pic.mybatis;

import org.apache.ibatis.logging.Log;

/**
 * Created by yuxh on 2018/8/27
 */
public class MyBatisLog implements Log {
    private String action = "";

    public MyBatisLog(String actionClass) {
        String[] ary = actionClass.split("\\.");
        this.action = ary[ary.length - 1];
    }


    @Override
    public boolean isDebugEnabled() {
        return true;
    }

    @Override
    public boolean isTraceEnabled() {
        return true;
    }

    @Override
    public void error(String s, Throwable throwable) {
        System.out.println(s);
    }

    @Override
    public void error(String s) {
        System.out.println(s);
    }

    @Override
    public void debug(String s) {
        System.out.println(s);
    }

    @Override
    public void trace(String s) {
        System.out.println(s);
    }

    @Override
    public void warn(String s) {
        System.out.println(s);
    }
}

配置

 <logger name="java.sql.Connection" level="DEBUG">
              <appender-ref ref="STDOUT"/>
       </logger>
       <logger name="java.sql.Statement" level="DEBUG">
              <appender-ref ref="STDOUT"/>
       </logger>
       <logger name="java.sql.PreparedStatement" level="DEBUG">
              <appender-ref ref="STDOUT"/>
       </logger>
       <logger name="org.apache.ibatis" level="DEBUG">
              <appender-ref ref="STDOUT"/>
       </logger>
       <logger name="java.sql" level="debug">
              <appender-ref ref="STDOUT"/>
       </logger>

设置参数:

mapUnderscoreToCamelCase: true -> 数据库自动映射到小驼峰字段.

posted @ 2018-05-06 11:07  NewSea  阅读(229)  评论(0编辑  收藏  举报