01.上下文配置

MyBatis 上下文配置

MyBatis 是一个半自动化的 SQL 辅助工具,在 MyBatis 的生命周期中,常见有以下几个组件,如果我们自己手动创建具体的实例对象的话,那么就需要关注于每个实例的生命周期。

  • SqlSessionFactoryBuilder: SqlSessionFactoryBuilder 可被重用创建多个 SqlSessionFactory 实例,建议仅将其保存为局部方法变量。

  • SqlSessionFactory: SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,在应用运行期间不要重复创建多次

  • SqlSession: SqlSession 的实例不是线程安全的,因此每个线程都应该有它自己的 SqlSession 实例;绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。

  • 映射器实例(Mapper Instances): 映射器是创建用来绑定映射语句的接口,映射器接口的实例是从 SqlSession 中获得的。映射器实例的最佳范围是方法范围,也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可废弃。并不需要显式地关闭映射器实例,尽管在整个请求范围(Request Scope)保持映射器实例也不会有什么问题,但是很快你会发现,像 SqlSession 一样,在这个范围上管理太多的资源的话会难于控制。

SqlSessionFactory

手工创建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="defaultStatementTimeout" value="3000"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="useGeneratedKeys" value="true"/>
    </settings>

    <!-- Continue going here -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://<your_db_host>:<your_db_port>/<your_db_name>"/>
                <property name="username" value="<your_db_username>"/>
                <property name="password" value="<your_db_password>"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>

我们也可以直接从 Java 程序而不是 XML 文件中创建 configuration,或者创建你自己的 configuration 构建器,MyBatis 也提供了完整的配置类,提供所有和 XML 文件相同功能的配置项。

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

SqlSession session = sqlSessionFactory.openSession();

// 直接使用 SQL 语句
try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

// 或者使用 Mapper
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

Mapper 即是 MyBatis 的核心魅力所在,其支持基于 XML 与基于注解的 Mapper 定义方式:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

或者直接在接口上添加注解并使用:

public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

SqlSession

每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:

下面的示例就展示了这个实践:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // do work
} finally {
  session.close();
}

在你的所有的代码中一致性地使用这种模式来保证所有数据库资源都能被正确地关闭。

Spring Boot 中集成使用

SPRING INITIALIZR 可以直接创建包含 MyBatis 的项目模板,也可以前往 Backend-Boilerplate/spring 查看相关模板。我们首先需要引入 org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.2 依赖,然后在 Application 中添加 Mapper 扫描路径,或者在相关的 Mapper 类中添加注解:

// 自定义 Mapper
@SpringBootApplication(scanBasePackages = "wx")
@MapperScan(basePackages = "wx")
@Slf4j
public class Application {}

@Mapper
public interface UserMapper {}

然后我们可以在 Spring Boot 的 application.properties 文件中添加 MyBatis 配置参数:

# application.properties
mybatis.config-location=classpath:/mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.domain.model
mybatis.type-handlers-package=com.example.typehandler
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-fetch-size=100
mybatis.configuration.default-statement-timeout=30

然后在 MyBatis 配置文件中,接下来我们如常定义实体类:

public class City implements Serializable {
  ...
}

与基于注解的 Mapper 类:

@Mapper
public interface CityMapper {
  @Select("SELECT id, name, state, country FROM city WHERE state = #{state}")
  City findByState(String state);
}

在使用的地方直接注入该 Mapper 类实例即可:

@Autowired
CityMapper cityMapper;

this.cityMapper.findByState("CA");

多数据源切换

缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache
  <!--可选参数 -->
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

值得一提的是,缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。而开启二级缓存后,缓存的配置和缓存实例会被绑定到 SQL 映射文件的命名空间中。同一命名空间中的所有语句和缓存将通过命名空间绑定在一起。每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。

默认情况下,语句会这样来配置:

<!-- 将 useCache 设置为 false 以忽略某条语句的缓存 -->
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

其意味着,映射语句文件中的所有 select 语句的结果将会被缓存,而映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存;并且二级缓存是事务性的,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存,并且会根据指定的刷新间隔来更新。默认是会保存列表或对象(无论查询方法返回哪种)的 1024 个引用,并且缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

自定义缓存

我们可以添加自定义的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<cache type="com.domain.something.MyCustomCache"/>

type 属性指定的类必须实现 org.mybatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

日志

Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理:SLF4J, Apache Commons Logging, Log4j 2,Log4j, JDK logging 等。MyBatis 内置日志工厂基于运行时自省机制选择合适的日志工具,它会使用第一个查找得到的工具(按上文列举的顺序查找);如果一个都未找到,日志功能就会被禁用。

在 mybatis-config.xml 中,我们可以手动指定使用的日志框架:

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>

这里以 Log4j 为例,如果我们需要打印如下 Mapper 的日志:

package org.mybatis.example;

public interface BlogMapper {
  @Select("SELECT * FROM blog WHERE id = #{id}")
  Blog selectBlog(int id);
}

那么在 Log4j 的配置文件中,就需要指定该 Mapper 的日志等级:

# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

添加以上配置后,Log4J 就会记录 org.mybatis.example.BlogMapper 的详细执行操作,且仅记录应用中其它类的错误信息(若有)。我们也可以将日志的记录方式从接口级别切换到语句级别,从而实现更细粒度的控制:

log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE
下一页