简介
使用了 MyBatis,在与数据库交互时,可以不必再为下面的事情烦恼:
- JDBC 代码: 设置 Driver,得到 Connection
- 手动设置参数:设置 JDBC 参数
- 解析结果集:解析 ResultSet 结果
MyBatis 怎么解决的呢?
- JDBC 代码:使用 xml 配置
- 手动设置参数:使用 xml 配置
- 解析结果集:将数据库中的记录,映射到 Java 接口 + POJO(Plain Old Java Object,普通)对象。
MyBatis 是一个常用的 ORM(Object Relational Mapping)框架。
第一个例子
准备数据源
MySQL 数据库
建表和准备测试数据
CREATE DATABASE test;
CREATE TABLE blog (
id INT NOT NULL,
author_id INT NOT NULL,
title VARCHAR(255),
PRIMARY KEY (id)
);
INSERT INTO blog (id,author_id,title) VALUES (1,101,'Jim Business');
INSERT INTO blog (id,author_id,title) VALUES (2,102,'Bally Slog');
Java 的 pom 依赖
pom 依赖:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
本文写作时,最新的版本为 3.4.6
项目结构
项目中共有 6 个文件,3 个 Java 文件,3 个资源文件。
.
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── org
│ │ │ └── apache
│ │ │ └── ibatis
│ │ │ ├── Blog.java
│ │ │ ├── BlogMapper.java
│ │ │ └── MainMybatis.java
│ │ └── resources
│ │ ├── BlogMapper.xml
│ │ ├── logback.xml
│ │ └── mybatis-config.xml
│ └── test
│ ├── java
│ └── resources
└── target
Blog
package org.apache.ibatis;
import java.io.Serializable;
public class Blog implements Serializable {
private Integer id;
private String title;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Blog [id=" + id + ", title=" + title + "]";
}
}
BlogMapper
package org.apache.ibatis;
public interface BlogMapper {
Blog selectBlog(Integer id);
}
MainMybatis
package org.apache.ibatis;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MainMybatis {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1);
System.out.println(blog);
} finally {
session.close();
}
}
}
BlogMapper.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">
<mapper namespace="org.apache.ibatis.BlogMapper">
<select id="selectBlog" resultType="org.apache.ibatis.Blog">
select * from Blog where id = #{id}
</select>
</mapper>
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<logger name="org.apache.ibatis" level="debug" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
mybatis-config.xml
<?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>
<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/test" />
<property name="username" value="note" />
<property name="password" value="abeffect" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="BlogMapper.xml" />
</mappers>
</configuration>
执行效果
09:59:48,355 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
09:59:48,355 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
09:59:48,355 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/Users/note/eclipse-workspace/testMybatis/target/classes/logback.xml]
09:59:48,399 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set
09:59:48,399 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [ch.qos.logback.core.ConsoleAppender]
09:59:48,409 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
09:59:48,461 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead.
09:59:48,461 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.
09:59:48,461 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details
09:59:48,462 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting level of logger [org.apache.ibatis] to DEBUG
09:59:48,462 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting additivity of logger [org.apache.ibatis] to false
09:59:48,462 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[org.apache.ibatis]
09:59:48,463 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
09:59:48,463 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
09:59:48,464 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
09:59:48,464 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@47d384ee - Registering current configuration as safe fallback point
2018-06-10 09:59:48 [main] DEBUG org.apache.ibatis.logging.LogFactory -
Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2018-06-10 09:59:48 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
PooledDataSource forcefully closed/removed all connections.
2018-06-10 09:59:48 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
PooledDataSource forcefully closed/removed all connections.
2018-06-10 09:59:48 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
PooledDataSource forcefully closed/removed all connections.
2018-06-10 09:59:48 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
PooledDataSource forcefully closed/removed all connections.
2018-06-10 09:59:48 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction -
Opening JDBC Connection
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
Sun Jun 10 09:59:48 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
2018-06-10 09:59:49 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
Created connection 690339675.
2018-06-10 09:59:49 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction -
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2925bf5b]
2018-06-10 09:59:49 [main] DEBUG o.a.ibatis.BlogMapper.selectBlog -
==> Preparing: select * from Blog where id = ?
2018-06-10 09:59:49 [main] DEBUG o.a.ibatis.BlogMapper.selectBlog -
==> Parameters: 1(Integer)
2018-06-10 09:59:49 [main] DEBUG o.a.ibatis.BlogMapper.selectBlog -
<== Total: 1
Blog [id=1, title=Jim Business]
MyBatis 执行过程
在完成了第一个例子之后,回过头来,继续看其是怎么执行的。
构建 SqlSessionFactory
MyBatis 都是从 SqlSessionFactory 开始的。
SqlSessionFactory 实例通过 SqlSessionFactoryBuilder 来构建得到。
可以通过 XML 来构建,如上面的例子。
也可以通过 Configuration 实例来构建,这个细节这里略过。这兴趣直接阅读不使用 XML 构建 SqlSessionFactory。
获取 SqlSession,执行 SQL 方法
SqlSessionFactory,就是制造 SqlSession 的工厂类。
而 SqlSession,包含了操作数据库的所有 SQL 方法。
这些方法都是在配置文件中已经映射过的 SQL 方法。
如示例中映射过的 selectBlog
方法。
具体的代码片断为:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1);
其它特征
通过注解映射 SQL 语句
第一个示例中,将 BlogMapper 中的接口方法,映射到了 BlogMapper.xml 中的 mapper 实现中。
这样写略微有些繁琐。
MyBatis 也支持在接口类中,直接通过注解的方式来写实现的。
举个例子吧。这里增加一个 BlogMapper2 接口类,实现与 BlogMapper 同样的效果。
增加类文件 BlogMapper2.class
package org.apache.ibatis;
import org.apache.ibatis.annotations.Select;
public interface BlogMapper2 {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(Integer id);
}
其中包含有一个 @Select
注解,实现了 BlogMapper.xml
的效果。
此外修改一下 SqlSessionFactory 的配置文件 mybatis-config.xml
,注册对应的 Mapper。否则会报异常 “Type interface is not known to the MapperRegistry”
。
修改后的片断如下:
<mappers>
<mapper resource="BlogMapper.xml" />
<mapper class="org.apache.ibatis.BlogMapper2" />
</mappers>
MainMybatis 中也修改一下:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper2 mapper = session.getMapper(BlogMapper2.class);
Blog blog = mapper.selectBlog(1);
System.out.println(blog);
} finally {
session.close();
}
执行效果片断如下:
2018-06-10 11:07:30 [main] DEBUG o.a.i.d.pooled.PooledDataSource -
Created connection 2079179914.
2018-06-10 11:07:30 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction -
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
2018-06-10 11:07:30 [main] DEBUG o.a.ibatis.BlogMapper2.selectBlog -
==> Preparing: SELECT * FROM blog WHERE id = ?
2018-06-10 11:07:30 [main] DEBUG o.a.ibatis.BlogMapper2.selectBlog -
==> Parameters: 1(Integer)
2018-06-10 11:07:30 [main] DEBUG o.a.ibatis.BlogMapper2.selectBlog -
<== Total: 1
Blog [id=1, title=Jim Business]
更详细的使用介绍
作用域(Scope)
SqlSessionFactoryBuilder
在构建完 SqlSessionFactory 后就没用了。
SqlSessionFactory
单例
SqlSession
SqlSession 不是线程安全的,不能被共享。在请求后,要及时的关闭。
如
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
Mapper
mapper 是从 SqlSession 中得到的,其作用域和 SqlSession 是相同的。
如
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// do work
} finally {
session.close();
}
XML 配置文件
这里记录几个细节
加载外部配置文件
<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>
这样就直接把配置文件写在外面的 config.properties
中了,而不用每次修改 xml 文件。
另一种加载外部配置文件方法
也可以在构造 SqlSessionFactory 时,将 properties 做为参数传入。如:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, props);
或者
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props);
这几种方式中,优先级从高到低为:
- 从 build 参数中传入
- 从 resource 属性读入
- properties 元素体内指定
其它
还可以:
- 设置属性的默认值
- 使用三元运算符
具体看官方文档
settings
这里记录一部分,完整请看官方文档。
设置参数 | 描述 | 默认值 |
---|---|---|
mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射 |
False |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE |
示例如下:
<settings>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
映射器(mappers)
使用相对于类路径的资源引用
<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>
其它
- typeAliases: 为 Java 类型设置一个短的名字
- typeHandlers: 转换 JDBC 类型和 Java 类型的值
- 处理枚举类型: 使用 EnumTypeHandler 或者 EnumOrdinalTypeHandler
- 对象工厂(objectFactory): 创建结果对象的新实例的工厂
- 插件(plugins): 可以在已映射语句执行过程中的某一点进行拦截调用
- 配置环境(environments): 每个数据库对应一个 SqlSessionFactory 实例
- databaseIdProvider: 根据不同的数据库厂商执行不同的语句
参考
附录
本文写作背景
提供 MySQL 兼容接口
Palo 是百度开源的一款 OLAP 引擎,提供 MySQL 兼容的接口。
这样用户在使用的时候,不需要关心其内容实现过程,将其当成 MySQL 来使用即可。
阿里云的 HybirdDB for MySQL 也是一款类似的产品,同样提供了 MySQL 兼容的接口。
最近在调研如何把我们的服务也以类似的方式以 MySQL 兼容接口提供给用户。
调用 MySQL 兼容接口
在最初的最初,先以调用方的视角,看看调用方怎么调用 MySQL 的吧。
因此,本文从最广泛使用的 MyBatis 切入,来体验了一下如何调用 MySQL 服务。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于