Mybatis
笔记
一、Mybatis
简介
【Mybatis
官网文档】https://mybatis.org/mybatis-3/index.html
【Mybatis
| Github
地址】https://github.com/mybatis/mybatis-3
1.1、什么是 MyBatis
MyBatis
是一款优秀的持久层框架,它支持自定义 SQL
、存储过程以及高级映射。MyBatis
免除了几乎所有的 JDBC
代码以及设置参数和获取结果集的工作。MyBatis
可以通过简单的 XML
或注解来配置和映射原始类型、接口和 Java POJO
(Plain Old Java Objects
,普通老式 Java
对象)为数据库中的记录。
1.2、如何获取Mybatis
Maven
仓库Github
仓库
1.3、为什么需要Mybatis
传统的JDBC
代码太复杂了,简化了持久层的实现。
1.4、Mybatis
的特点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个
jar
文件+配置几个sql
映射文件。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。 - 灵活:
mybatis
不会对应用程序或者数据库的现有设计强加任何影响。sql
写在xml
里,便于统一管理和优化。通过sql
语句可以满足操作数据库的所有需求。 - 解除
sql
与程序代码的耦合:通过提供DAO
层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql
和代码的分离,提高了可维护性。 - 提供映射标签,支持对象与数据库的
ORM
字段关系映射。 - 提供对象关系映射标签,支持对象关系组建维护。
- 提供
xml
标签,支持编写动态sql
。
二、第一个Mybatis
程序(重点)
2.1、数据库准备
准备好一个mysql
的数据库,包括一张表user
。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2.2、创建项目和配置
新建一个普通的maven
项目。
2.2.1、导入mybatis
依赖
导入mybatis
依赖,以及一些其他相关依赖。
<dependencies>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- Mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.2.2、设置资源过滤(可选)
由于maven
默认的资源文件在resources
路径下,如果在其他位置放置xml
文件,还需要进行对应路径下的配置,防止资源过滤。
<build>
<resources>
<!-- 对 src/main/java 路径的 xml 和 properties 文件 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!-- 取消导出过滤 -->
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2.2.3、mybatis
核心配置文件
编写mybatis
的核心配置文件(放在路径src/main/resources/mybatis-config.xml
下)。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置 -->
<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://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="xxxxxx"/>
</dataSource>
</environment>
</environments>
<!-- 注册 xml 映射文件(每一个映射文件都必须注册) -->
<mappers>
<mapper resource="com/wzq/dao/UserMapper.xml"/>
</mappers>
</configuration>
2.2.4、mybatis
工具类
编写mybatis
工具类(放在路径src/main/<相应包名>
下)。
package com.wzq.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
// sqlSessionFactory 工厂模式 -> 生产 sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 获取 sqlSessionFactory 实例
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e){
e.printStackTrace();
}
}
// 通过 sqlSessionFactory 实例,获取 sqlSession 实例
// sqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
// return sqlSessionFactory.openSession(true); // 设置自动提交事务
}
}
2.3、编写业务代码
2.3.1、实体类
POJO
实体类(对应数据库中的user
表)。
package com.wzq.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
2.3.2、Mapper
接口
Mapper
接口。
package com.wzq.dao;
import com.wzq.pojo.User;
import java.util.List;
public interface UserMapper {
// 查询全部用户
List<User> getUserList();
// 查询用户
User getUserById(int id);
// 新增用户
int addUser(User user);
// 修改用户
int updateUser(User user);
// 删除用户
int deleteUser(int id);
}
2.3.3、xml
映射文件
xml
映射文件(存放在任意位置均可,一般放在resources
文件夹中,并和源码路径保持一致,即resources/com/wzq/dao/UserMapper.xml
)。
<?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">
<!-- namespace: bound a Mapper Interface -->
<mapper namespace="com.wzq.dao.UserMapper">
<!-- select -->
<select id="getUserList" resultType="com.wzq.pojo.User">
select * from mybatis.user;
</select>
<select id="getUserById" parameterType="int" resultType="com.wzq.pojo.User">
select * from mybatis.user where id = #{id};
</select>
<!-- insert -->
<insert id="addUser" parameterType="com.wzq.pojo.User">
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
<!-- update -->
<update id="updateUser" parameterType="com.wzq.pojo.User">
update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id};
</update>
<!-- delete -->
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>
</mapper>
2.4、junit
测试
package com.wzq.dao;
import com.wzq.pojo.User;
import com.wzq.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
// 获得 sqlSession 对象
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行 SQL 应用逻辑
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
@Test
public void getUserById(){
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
}
}
@Test
public void addUser(){
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.addUser(new User(4, "Kitty", "123333"));
if(res > 0){
System.out.println("插入成功");
}
sqlSession.commit(); // 提交事务
}
}
@Test
public void updateUser(){
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.updateUser(new User(4, "Really", "123456"));
if(res > 0){
System.out.println("修改成功");
}
sqlSession.commit(); // 提交事务
}
}
@Test
public void deleteUser(){
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
int res = userMapper.deleteUser(4);
if(res > 0){
System.out.println("删除成功");
}
sqlSession.commit(); // 提交事务
}
}
}
三、配置解析
3.1、核心配置文件
3.2、属性
属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java
属性文件中配置这些属性,也可以在 properties
元素的子元素中设置。例如:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。例如:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
具体使用示例:
如果一个属性在不只一个地方进行了配置,那么,
MyBatis
将按照下面的顺序来加载:
- 首先读取在
properties
元素体内指定的属性。- 然后根据
properties
元素中的resource
属性读取类路径下属性文件,或根据url
属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。- 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,
resource/url
属性中指定的配置文件次之,最低优先级的则是properties
元素中指定的属性。
3.3、设置
常用的 settings
元素的示例如下。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="logImpl" value="SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING"/>
</settings>
3.4、类型别名(可选)
类型别名可为 Java
类型设置一个缩写名字。 它仅用于 XML
配置,意在降低冗余的全限定类名书写。例如:
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
当这样配置时,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一个包名,MyBatis
会在包名下面搜索需要的 Java Bean
,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
每一个在包 domain.blog
中的 Java Bean
,在没有注解的情况下,会使用 Bean
的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
3.5、环境配置
MyBatis
可以配置成适应多种环境,这种机制有助于将 SQL
映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema
的多个生产数据库中使用相同的 SQL
映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory
实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory
实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
- 每个数据库对应一个
SqlSessionFactory
实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder
即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境。
environments
元素定义了如何配置环境。
<!-- 默认使用 development 环境 -->
<environments default="development">
<!-- 每个环境具有自己的 id -->
<environment id="development">
<!-- 事务管理器的配置 -->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<!-- 数据源的配置 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
环境可以随意命名,但务必保证默认的环境 ID
要匹配其中一个环境 ID
。
3.6、映射器
既然 MyBatis
的行为已经由上述元素配置完了,我们现在就要来定义 SQL
映射语句了。 但首先,我们需要告诉 MyBatis
到哪里去找到这些语句。
在自动查找资源方面,Java
并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis
到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:///
形式的 URL
),或类名和包名等。例如:
<!-- 使用相对于类路径的资源引用() -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
这些配置会告诉 MyBatis
去哪里找映射文件,剩下的细节就应该是每个 SQL
映射文件了。
四、作用域和生命周期
理解不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
【提示】对象生命周期和依赖注入框架
依赖注入框架可以创建线程安全的、基于事务的
SqlSession
和映射器,并将它们直接注入到你的bean
中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用MyBatis
感兴趣,可以研究一下MyBatis-Spring
或MyBatis-Guice
两个子项目。
SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory
,就不再需要它了。 因此 SqlSessionFactoryBuilder
实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder
来创建多个 SqlSessionFactory
实例,但最好还是不要一直保留着它,以保证所有的 XML
解析资源可以被释放给更重要的事情。
SqlSessionFactory
SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory
的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory
被视为一种代码“坏习惯”。因此 SqlSessionFactory
的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession
实例。SqlSession
的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession
实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession
实例的引用放在任何类型的托管作用域中,比如 Servlet
框架中的 HttpSession
。 如果你现在正在使用一种 Web
框架,考虑将 SqlSession
放在一个和 HTTP
请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession
,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally
块中。 下面的示例就是一个确保 SqlSession
关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
五、结果映射(重点)
5.1、简单结果映射
resultMap
元素是 MyBatis
中最重要最强大的元素。它可以让你从 90%
的 JDBC
ResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC
不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap
能够代替实现同等功能的数千行代码。ResultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
之前你已经见过简单映射语句的示例,它们没有显式指定 resultMap
。比如:
<select id="selectUsers" resultType="map">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
上述语句只是简单地将所有的列映射到 HashMap
的键上,这由 resultType
属性指定。虽然在大部分情况下都够用,但是 HashMap
并不是一个很好的领域模型。你的程序更可能会使用 JavaBean
或 POJO
(Plain Old Java Objects
,普通老式 Java
对象)作为领域模型。MyBatis
对两者都提供了支持。看看下面这个 JavaBean
:
package com.someapp.model;
public class User {
private int id;
private String username;
private String hashedPassword;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getHashedPassword() {
return hashedPassword;
}
public void setHashedPassword(String hashedPassword) {
this.hashedPassword = hashedPassword;
}
}
基于 JavaBean
的规范,上面这个类有 3
个属性:id,username
和 hashedPassword
。这些属性会对应到 select
语句中的列名。
这样的一个 JavaBean
可以被映射到 ResultSet
,就像映射到 HashMap
一样简单。
<select id="selectUsers" resultType="com.someapp.model.User">
select id, username, hashedPassword
from some_table
where id = #{id}
</select>
在这些情况下,MyBatis
会在幕后自动创建一个 ResultMap
,再根据属性名来映射列到 JavaBean
的属性上。如果列名和属性名不能匹配上,可以在 SELECT
语句中设置列别名(这是一个基本的 SQL
特性)来完成匹配。比如:
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
在学习了上面的知识后,你会发现上面的例子没有一个需要显式配置 ResultMap
,这就是 ResultMap
的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置 ResultMap
。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap
会怎样,这也是解决列名不匹配的另外一种方式。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
然后在引用它的语句中设置 resultMap
属性就行了(注意我们去掉了 resultType
属性)。比如:
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
如果这个世界总是这么简单就好了。
5.2、高级结果映射
MyBatis
创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF
范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap
就是 MyBatis
对这个问题的答案。
比如,我们如何映射下面这个语句?
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 不用紧张,我们会一步一步地来说明。虽然它看起来令人望而生畏,但其实非常简单。
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<!-- 自身的单个属性 -->
<result property="title" column="blog_title"/>
<!-- 关联的对象 -->
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<!-- 集合的对象 -->
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
resultMap
元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap
元素的概念视图。
六、日志
Mybatis
通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
SLF4J
Apache Commons Logging
Log4j 2
Log4j(3.5.9起废弃)
JDK logging
MyBatis
内置日志工厂基于运行时自省机制选择合适的日志工具。它会使用第一个查找得到的工具(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。
不少应用服务器(如 Tomcat
和 WebShpere
)的类路径中已经包含 Commons Logging
,所以在这种配置环境下的 MyBatis
会把它作为日志工具,记住这点非常重要。这将意味着,在诸如 WebSphere
的环境中,它提供了 Commons Logging
的私有实现,你的 Log4J
配置将被忽略。MyBatis
将你的 Log4J
配置忽略掉是相当令人郁闷的(事实上,正是因为在这种配置环境下,MyBatis
才会选择使用 Commons Logging
而不是 Log4J
)。如果你的应用部署在一个类路径已经包含 Commons Logging
的环境中,而你又想使用其它日志工具,你可以通过在 MyBatis
配置文件 mybatis-config.xml
里面添加一项 setting
来选择别的日志工具。
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
logImpl
可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING
,或者是实现了接口 org.apache.ibatis.logging.Log
的,且构造方法是以字符串为参数的类的完全限定名。
你也可以调用如下任一方法来使用日志工具:
org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging();
如果你决定要调用以上某个方法,请在调用其它 MyBatis
方法之前调用它。另外,仅当运行时类路径中存在该日志工具时,调用与该日志工具对应的方法才会生效,否则 MyBatis
一概忽略。如你环境中并不存在 Log4J2
,你却调用了相应的方法,MyBatis
就会忽略这一调用,转而以默认的查找顺序查找日志工具。
七、使用注解开发
还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML
来配置,而可以使用 Java
注解来配置。例如:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
// 多个参数,必须要加上 @Param 注解
@Select("select * from user where id = #{id} and name = #{name}")
User getUserByID(@Param("id") int id, @Param("name") string name);
@Insent("insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})")
int addUser(User user);
@Update("update user set name = #(name}, pwd = #{pwd} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id" int id);
}
【注】使用注解的方式,也需要在mybatis
核心配置文件的<Mappers>
中进行注册,注册的方式同上3.6
所示。
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java
注解不仅力不从心,还会让本就复杂的 SQL
语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML
来映射语句。
选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML
的语句映射方式间自由移植和切换。
八、动态SQL
动态 SQL
是 MyBatis
的强大特性之一。如果你使用过 JDBC
或其它类似的框架,你应该能理解根据不同条件拼接 SQL
语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL
,可以彻底摆脱这种痛苦。
使用动态 SQL
并非一件易事,但借助可用于任何 SQL
映射语句中的强大的动态 SQL
语言,MyBatis
显著地提升了这一特性的易用性。
如果你之前用过 JSTL
或任何基于类 XML
语言的文本处理器,你对动态 SQL
元素可能会感觉似曾相识。在 MyBatis
之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL
的表达式,MyBatis 3
替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
if
choose (when, otherwise)
trim (where, set)
foreach
以下给出一个动态SQL
的查询示例,更多说明详见官网文档。
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
九、缓存
MyBatis
内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3
中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存(一级缓存),它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL
映射文件中添加一行:
<cache/>
基本上就是这样。这个简单语句的效果如下:
- 映射语句文件中的所有
select
语句的结果将会被缓存。 - 映射语句文件中的所有
insert、update
和delete
语句会刷新缓存。 - 缓存会使用最近最少使用算法(
LRU, Least Recently Used
)算法来清除不需要的缓存。 - 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的
1024
个引用。 - 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
这些属性可以通过 cache
元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO
缓存,每隔 60
秒刷新,最多可以存储结果对象或列表的 512
个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
评论区