MyBatis~从入门到入坑。
文章目录
中文文档:http://mybatis.org/spring/zh/index.html
Github: https://github.com/mybatis/mybatis-3
what。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis 本是 apache 的一个开源项目 iBatis,2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis 。2013 年 11 月迁移到 Github。
iBATIS 一词来源于 “internet” 和 “abatis” 的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAOs)。
当前,最新版本是 MyBatis 3.5.4 ,其发布时间是 2020 年 2 月 4 日。
Maven。
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
</dependencies>
数据持久化。
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程。
- 内存:断电即失。
- 数据库(jdbc)、io 文件持久化。
- 生活:冷藏、罐头。
为什么要持久化。
-
有一些对象,不能让 ta 丢失。
-
内存太贵。
持久层。
dao 层、service 层、controller 层。
-
完成持久化工作的代码块。
-
层界限十分明显。
why。
-
方便。
-
传统的 JDBC 太复杂了。懒。简化。框架。自动化。
-
不用 MyBatis 也可以。但更容易上手。
-
特点。
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个 jar 文件 + 配置几个 sql 映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis 不会对应用程序或者数据库的现有设计强加任何影响。sql 写在 xml 里,便于统一管理和优化。通过 sql 语句可以满足操作数据库的所有需求。
- 解除 sql 与程序代码的耦合:通过提供 DAO 层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql 和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的 orm 字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供 xml 标签,支持编写动态 sql。
最重要一点:使用人的多。
how?
CREATE DATABASE mybatis;
USE mybatis;
CREATE TABLE user (
id INT(20) NOT NULL,
name VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=UTF8;
INSERT INTO `mybatis`.`user` (`id`, `name`, `pwd`) VALUES ('1', 'geek', '123');
INSERT INTO `mybatis`.`user` (`id`, `name`, `pwd`) VALUES ('2', '张三', '123');
INSERT INTO `mybatis`.`user` (`id`, `name`, `pwd`) VALUES ('3', '李四', '123');
- 父工程。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.geek</groupId>
<artifactId>mybatis-geek</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>mybatis-00</module>
</modules>
<!-- 父工程。-->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
SqlSessionFactory ~ 从 XML 中构建 SqlSessionFactoryBuilder ~ sqlSessionFactoryBuilder.build();。
- mybatis-config.xml。
XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 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://192.168.142.143:3307/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<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>
<!-- org.apache.ibatis.binding.BindingException: Type interface com.geek.dao.UserMapper is not known to the MapperRegistry.-->
<!-- 每一个 Mapper.xml 都需要在 MyBatis 核心配置文件中注册。-->
<mappers>
<mapper resource="IUserMapper.xml"/>
</mappers>
</configuration>
当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 不使用 XML 构建 SqlSessionFactory。
如果你更愿意直接从 Java 代码而不是 XML 文件中创建配置,或者想要创建你自己的配置建造器,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);
注意该例中,configuration 添加了一个映射器类(mapper class)。映射器类是 Java 类,它们包含 SQL 映射注解从而避免依赖 XML 文件。不过,由于 Java 注解的一些限制以及某些 MyBatis 映射的复杂性,要使用大多数高级映射(比如:嵌套联合映射),仍然需要使用 XML 配置。有鉴于此,如果存在一个同名 XML 配置文件,MyBatis 会自动查找并加载它(在这个例子中,基于类路径和 BlogMapper.class 的类名,会加载 BlogMapper.xml)。具体细节稍后讨论。
既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。eg.
SqlSession ~ 从 SqlSessionFactory 中获取 SqlSession。
package com.geek.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 MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 使用 MyBatis 获取 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 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
编写代码。
- pojo。
package com.geek.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String pwd;
}
- dao。
package com.geek.dao;
import com.geek.pojo.User;
import java.util.List;
public interface IUserMapper {
List<User> getUserList();
}
在 dao 相同包下创建 IUserMapper.xml。
现在不需要用 UserMapperImpl 实现类了。
框架会根据 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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IUserDao">
<!-- 通过 com.geek.dao.IUserDao.getUserList(); 映射。-->
<select id="getUserList" resultType="com.geek.pojo.User">
select * from mybatis.user;
</select>
</mapper>
- 测试。
package com.geek.dao;
import com.geek.pojo.User;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void test() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// 方式一:getMapper。
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
List<User> userList = userMapper.getUserList();
// 方式二。
// List<Object> userList = sqlSession.selectList("com.geek.dao.UserMapper.getUserList");
System.out.println("userList = " + userList);
// 关闭 SqlSession。
sqlSession.close();
}
}
- test。
package com.geek.dao;
import com.geek.pojo.User;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void test() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// 方式一:getMapper。
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
List<User> userList = userMapper.getUserList();
// 方式二。
// List<Object> userList = sqlSession.selectList("com.geek.dao.IUserMapper.getUserList");
System.out.println("userList = " + userList);
// 关闭 SqlSession。
sqlSession.close();
}
@Test
public void test01() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// 方式一:getMapper。
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
User user = userMapper.getUserById(1);
System.out.println("user = " + user);
// 关闭 SqlSession。
sqlSession.close();
}
// 增删改需要提交事务。
@Test
public void testAddUser() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
int addUser = userMapper.addUser(new User(4, "哈哈", "123"));
System.out.println("addUser = " + addUser);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
// 增删改需要提交事务。
@Test
public void testUpdateUser() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
int updateUser = userMapper.updateUser(new User(4, "呵呵", "123"));
System.out.println("updateUser = " + updateUser);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
// 增删改需要提交事务。
@Test
public void testDeleteUser() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
int i = userMapper.deleteUser(4);
System.out.println("i = " + i);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
}
package com.geek.dao;
import com.geek.pojo.User;
import java.util.List;
public interface IUserMapper {
/**
* 查询全部用户。
*
* @return
*/
List<User> getUserList();
/**
* 根据 id 查询用户。
*
* @param id
* @return
*/
User getUserById(Integer id);
/**
* Insert 一个用户。
*
* @param user
* @return
*/
int addUser(User user);
/**
* 更新一个用户。
*
* @param user
* @return
*/
int updateUser(User user);
/**
* 删除一个用户。
*
* @param id
* @return
*/
int deleteUser(Integer id);
}
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IUserMapper">
<select id="getUserList" resultType="com.geek.pojo.User">
select * from mybatis.user;
</select>
<select id="getUserById" parameterType="int" resultType="com.geek.pojo.User">
select * from mybatis.user where id = #{id};
</select>
<insert id="addUser" parameterType="com.geek.pojo.User">
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
<update id="updateUser" parameterType="com.geek.pojo.User">
update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>
</mapper>
万能 map。
不需要写 User 的全部属性。直接从 map 中的 key 取需要的 key-value。
// 万能 map。
int addUser2(Map<String, Object> map);
// 增删改需要提交事务。
// 使用万能 map。
@Test
public void testAddUser2() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId", "666");
map.put("username", "哈哈");
map.put("password", "123");
int addUser2 = userMapper.addUser2(map);
System.out.println("addUser2 = " + addUser2);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
<!-- 万能 map。-->
<update id="addUser2" parameterType="map">
insert into mybatis.user (id, name, pwd) values (#{userId}, #{username}, #{password});
</update>
模糊查询。
- Java 代码传递能配符。
List<User> userList = mapper.getUserLike("%李%");
<select id="getUserLike" resultType="com.geek.pojo.User">
select * from mybatis.user where name like #{value};
</select>
- 在 sql 中拼接使用通配符。
List<User> userList = mapper.getUserLike("李");
<select id="getUserLike" resultType="com.geek.pojo.User">
select * from mybatis.user where name like "%"#{value}"%";
</select>
MyBatis 配置。
核心配置文件。
mybatis-config.xml。
https://mybatis.org/mybatis-3/zh/configuration.html
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
环境配置(environments)。
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
每个数据库对应一个 SqlSessionFactory 实例。
<?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://192.168.142.143:3307/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<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>
<!-- org.apache.ibatis.binding.BindingException: Type interface com.geek.dao.IUserMapper is not known to the MapperRegistry.-->
<!-- 每一个 Mapper.xml 都需要在 MyBatis 核心配置文件中注册。-->
<mappers>
<mapper resource="IUserMapper.xml"/>
</mappers>
</configuration>
事务管理器(transactionManager)
MyBatis 默认事务管理器:JDBC,连接池:POOLED。
在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”)。
-
JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
-
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>`
如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。
public interface TransactionFactory {
default void setProperties(Properties props) { // 从 3.5.2 开始,该方法为默认方法
// 空实现
}
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
在事务管理器实例化后,所有在 XML 中配置的属性将会被传递给 setProperties() 方法。你的实现还需要创建一个 Transaction 接口的实现类,这个接口也很简单:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
使用这两个接口,你可以完全自定义 MyBatis 对事务的处理。
数据源(dataSource)。
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type=“[ UNPOOLED | POOLED | JNDI]”):
- UNPOOLED。
这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性。
- driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
- url – 这是数据库的 JDBC URL 地址。
- username – 登录数据库的用户名。
- password – 登录数据库的密码。
- defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
- defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。
作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:
driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
- POOLED。
这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源。
- poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
- poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
- poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
- poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
- poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置,作用于每一个尝试从缓存池获取连接的线程。如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnections 与 poolMaximumLocalBadConnectionTolerance 之和。默认值:3(新增于 3.4.5)
- poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
- poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
- poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
- JNDI。
这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
- initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
- data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如。
- env.encoding=UTF8
这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8 的 encoding 属性。
你可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现。
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码。
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
public C3P0DataSourceFactory() {
this.dataSource = new ComboPooledDataSource();
}
}
为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。下面是一个可以连接至 PostgreSQL 数据库的例子:
<dataSource type="org.myproject.C3P0DataSourceFactory">
<property name="driver" value="org.postgresql.Driver"/>
<property name="url" value="jdbc:postgresql:mydb"/>
<property name="username" value="postgres"/>
<property name="password" value="root"/>
</dataSource>
属性(properties)。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如:
<!-- 直接配置。-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
- 也可以直接引入外部配置文件。(优先)。
使用 ${}
引用 <properties resource="db.properties"/>
指定的配置文件。
<?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>
<!-- 引入外部配置文件。-->
<properties resource="db.properties"/>
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.142.143:3307/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<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>
<!-- org.apache.ibatis.binding.BindingException: Type interface com.geek.dao.UserMapper is not known to the MapperRegistry.-->
<!-- 每一个 Mapper.xml 都需要在 MyBatis 核心配置文件中注册。-->
<mappers>
<mapper resource="IUserMapper.xml"/>
</mappers>
</configuration>
这个例子中的 username 和 password 将会由 properties 元素中设置的相应值来替换。 driver 和 url 属性将会由 db.properties 文件中对应的值来替换。这样就为配置提供了诸多灵活选择。
- db.properties。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.142.143:3307/mybatis?characterEncoding=UTF-8
username=root
password=root
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
类型别名(typeAliases)。
类型别名可为 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 {
...
}
- 起别名。
<!-- 给实体类起别名。-->
<typeAliases>
<typeAlias type="com.geek.pojo.User" alias="user"/>
</typeAliases>
<select id="getUserList" resultType="user">
select * from mybatis.user;
</select>
- 包名。
<!-- 给实体类起别名。-->
<typeAliases>
<!--<typeAlias type="com.geek.pojo.User" alias="user"/>-->
<package name="com.geek.pojo"/>
</typeAliases>
大写也可以。
<select id="getUserList" resultType="User">
select * from mybatis.user;
</select>
- 一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
设置(settings)。
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true / false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true / false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 | true / false | false (在 3.4.1 及之前的版本中默认为 true) |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true / false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true / false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true / false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。 | NONE: 不做任何反应。
|
WARNING: 输出警告日志。(‘org.apache.ibatis.session.AutoMappingUnknownColumnBehavior’ 的日志等级必须设置为 WARN)
FAILING: 映射失败 (抛出 SqlSessionException) | NONE, WARNING, FAILING | NONE
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null)
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null)
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) | 未设置 (null)
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true / false | False
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true / false | True
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true / false | False
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true / false | false
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true / false | false
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上)
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true / false | true
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置
shrinkWhitespacesInSql | 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) | true / false | false
defaultSqlProviderType | Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted. | A type alias or fully qualified class name | Not set
映射器(mappers)。
既然 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 映射文件了,也就是接下来我们要讨论的。
映射器 mappers~细节。
- 如果是在 resources 目录下。
目录结构要以 /
分隔,而不是 .
。
<mappers>
<mapper resource="com/geek/dao/IUserMapper.xml"/>
<!--<mapper class="com.geek.dao.IUserMapper"/>-->
</mappers>
- 使用 class 文件绑定注册。
<mappers>
<!--<mapper resource="com/geek/dao/IUserMapper.xml"/>-->
<mapper class="com.geek.dao.IUserMapper"/>
</mappers>
- 接口和 Mapper 配置文件必须同名。
- 接口和 Mapper 配置文件必须在同一包下。
Mapper 文件可以写在 mapper 包下(和 Mapper 接口在一起),
也可以在 resources 目录下 ~ 创建时目录结构要以/
分隔,而不是.
。
- 使用 package。
<mappers>
<!--<mapper resource="com/geek/dao/IUserMapper.xml"/>-->
<!--<mapper class="com.geek.dao.IUserMapper"/>-->
<package name="com.geek.dao"/>
</mappers>
- 接口和 Mapper 配置文件必须同名。
- 接口和 Mapper 配置文件必须在同一包下。
Mapper 文件可以写在 mapper 包下(和 Mapper 接口在一起),
也可以在 resources 目录下 ~ 创建时目录结构要以/
分隔,而不是.
。
作用域(Scope)和生命周期。
提示 对象生命周期和依赖注入框架
依赖注入框架可以创建线程安全的、基于事务的 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()) {
// 你的应用逻辑代码
}
在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。
每一个 mapper,代表一个具体业务。
映射器实例
映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}
ResultMap ~ 解决属性名和字段名不一致的问题。
不一致则查出来为空。
解决。
- 起别名。
select * from mybatis.user;
select id, name, pwd from mybatis.user;
select id, name, pwd as password from mybatis.user;
- ResultMap。
<!-- 结果集映射。-->
<resultMap id="UserMap" type="User">
<!-- property~实体类中的属性。column~数据库中的字段。-->
<!--<result property="id" column="id"/>-->
<!--<result property="name" column="name"/>-->
<result property="password" column="pwd"/>
</resultMap>
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#Result_Maps
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
如果这个世界总是这么简单就好了。
…
日志。
日志工厂。
SLF4J
LOG4J
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING
NO_LOGGING
- 配置日志。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
log4j。
Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
import org.apache.log4j.Logger;
分页。
select * from mybatis.user limit 0, 3;
select * from mybatis.user limit 3
select * from mybatis.user limit 0, -1
// 这是一个历史 bug。已修复(7 Dec 2003 11:02)。
https://bugs.mysql.com/bug.php?id=2037
public interface IUserMapper {
List<User> getUserByLimit(Map<String, Object> map);
<!-- 分页。-->
<select id="getUserByLimit" parameterType="map" resultType="user">
select * from user limit #{startIndex}, #{pageSize}
</select>
@Test
public void testLimit() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper userMapper = sqlSession.getMapper(IUserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<User> userList = userMapper.getUserByLimit(map);
System.out.println("userList = " + userList);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
RowBounds 分页。
<!-- 分页 2。-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from user
</select>
// 分页 2。
List<User> getUserByRowBounds();
@Test
public void testRowBounds() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// rowBounds。
RowBounds rowBounds = new RowBounds(1, 2);
// 通过 Java 代码层面实现分页。
List<User> userList = sqlSession.selectList("com.geek.dao.IUserMapper.getUserByRowBounds", null, rowBounds);
System.out.println("~ ~ ~ ~ ~ ~ ~ userList = " + userList);
// 提交事务。
sqlSession.commit();
// 关闭 SqlSession。
sqlSession.close();
}
分页插件~pagehelper。
https://pagehelper.github.io/
注解。
面向接口编程。
我们在一般实现一个系统的时候,通常是将定义与实现合为一体,不加分离的,我认为最为理想的系统设计规范应是所有的定义与实现分离,尽管这可能对系统中的某些情况有点麻烦。
大家之前都学过面向对象编程,也学习过接但在真正的开发中,很多时候我们会选择面向接口编程。
根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了。
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
关于接口的理解。
- 接口从更深层次的理解,应是定义( 规范,约束) 与实现( 名实分离的原则) 的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类。
- 第一类是对一个个体的抽象。ta 可对应为一个抽象体(abstract class)。
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)。
- 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
- 三个面向区别。
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法。
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现。
- 接口设计与非接口设计是针对复用技术而言的,与面向对象〈过程)不是一个问题。更多的体现就是对系统整体的架构。
使用注解开发。
对于像 BlogMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置。
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
选择何种方式来配置映射,以及认为是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 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>
<!-- 引入外部配置文件。(优先)。-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<settings>
<!-- 标准日志实现。-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- log4j。-->
<!--<setting name="logImpl" value="LOG4J"/>-->
</settings>
<!-- 给实体类起别名。-->
<typeAliases>
<!--<typeAlias type="com.geek.pojo.User" alias="user"/>-->
<package name="com.geek.pojo"/>
</typeAliases>
<environments default="test">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.142.143:3307/mybatis?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
package com.geek.dao;
import com.geek.pojo.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
public interface IUserMapper {
/**
* 查询全部用户。
*
* @return
*/
@Select("select * from user")
List<User> getUserList();
}
自动提交事务。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);// 自动 commit。
}
@param()。
- 基本类型的参数或者 String 类型,需要加上。
- 引用类型不需要加。
- 如果只有一个基本类型的话,可以忽略。但是建议大家都加上!
- 我们在 SQL 中引用的就是我们这里的 @Param() 中设定的属性名。
package com.geek.dao;
import com.geek.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface IUserMapper {
/**
* 查询全部用户。
*
* @return
*/
@Select("select * from user")
List<User> getUserList();
// 方法存在多个参数,所有参数前必须加 @Param。
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id);
}
@Test
public void testSelect() {
// 获得 SqlSession 对象。
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User user = mapper.getUserById(1);
System.out.println("user = " + user);
// 关闭 SqlSession。
sqlSession.close();
}
增删改查。
package com.geek.dao;
import com.geek.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface IUserMapper {
/**
* 查询全部用户。
*
* @return
*/
@Select("select * from user")
List<User> getUserList();
// 方法存在多个参数,所有参数前必须加 @Param。
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id);
@Insert("insert into user(id, name, pwd) values (#{id}, #{name}, #{password})")
int addUser(User user);
@Update("update user set name = #{name}, pwd = #{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
}
注意:我们必须要将接囗注册绑定到我们的核心配置文件中!
#{} ${} 区别。
Lombok。
复杂查询。
多对一。
CREATE TABLE `mybatis`.`teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARACTER SET=UTF8;
INSERT INTO `mybatis`.`teacher` (`id`, `name`) VALUES ('1', '李老师');
CREATE TABLE `mybatis`.`student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`)
REFERENCES `mybatis`.`teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARACTER SET=UTF8;
INSERT INTO `mybatis`.`student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `mybatis`.`student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `mybatis`.`student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `mybatis`.`student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `mybatis`.`student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
- pojo。
package com.geek.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
// 学生需要关联一个老师。
private Teacher teacher;
// private int tid;
}
package com.geek.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
}
- sql。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IStudentMapper">
<!--
思路。
查询所有的学生信息。
根据查出来的学生的 tid,查找对应的老师。
-->
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 复杂的属性需要单独处理。
对象:association。
集合:collection。
-->
<!-- 使用学生表中的 tid 去老师表查询老师,转换为 javaType:Teacher。-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
<!--<collection property=""/>-->
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
// 以下为中间测试过程。Java Bean 的 teacher 为空。
<!-- <select id="getStudent" resultType="Student">
-- select * from student s, teacher t where s.tid = t.id;
SELECT
s.id, s.name, t.name
FROM
student s,
teacher t
WHERE
t.id = s.tid;
-- Java Bean 的 teacher 为空。
</select>-->
</mapper>
- 测试。
@Test
public void testStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IStudentMapper mapper = sqlSession.getMapper(IStudentMapper.class);
List<Student> studentList = mapper.getStudent();
studentList.forEach(System.out::println);
sqlSession.close();
}
方式二。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IStudentMapper">
<!-- 方式 2。-->
<!-- 按照结果嵌套处理。-->
<select id="getStudent2" resultMap="StudentTeacher2">
SELECT
s.id sid, s.name sname, t.name tname
FROM
student s,
teacher t
WHERE
t.id = s.tid;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- 复杂的属性需要单独处理。
对象:association。
集合:collection。
-->
<!-- 使用学生表中的 tid 去老师表查询老师,转换为 javaType:Teacher。-->
<association property="teacher" javaType="Teacher">
<!-- class Teacher 的 name 对应表中的 tname。-->
<result property="name" column="tname"/>
</association>
<!--<collection property=""/>-->
</resultMap>
</mapper>
一对多。
一个老师 ——> 多个学生。
- pojo 实体类。
package com.geek.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String name;
private int tid;
}
package com.geek.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private int id;
private String name;
// 一个老师拥有多个学生。
private List<Student> students;
// private Student student;
}
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.ITeacherMapper">
<!-- 按结果嵌套查询。-->
<select id="getTeacher" resultMap="TeacherStudent">
SELECT
s.id sid, s.name sname, t.name tname, t.id tid
FROM
student s,
teacher t
WHERE
t.id = s.tid AND t.id = #{tid};
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result column="tid" property="id"/>
<result column="tname" property="name"/>
<!-- 复杂的属性需要单独处理。
对象:association。
集合:collection。
javaType="Student"。指定属性的类型。
ofType="Student"。集合中的泛型信息。(List<Student>)。
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<!--<select id="getTeacher" resultType="Teacher">-->
<!--select * from mybatis.teacher;-->
<!--</select>-->
</mapper>
package com.geek;
import com.geek.dao.ITeacherMapper;
import com.geek.pojo.Teacher;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest {
@Test
public void test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
ITeacherMapper mapper = sqlSession.getMapper(ITeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println("teacher = " + teacher);
// teacher = Teacher(id=1, name=李老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
sqlSession.close();
}
}
方法二。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.ITeacherMapper">
<!-- 方式二。-->
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!--<result property="id" column="id"/>-->
<!-- ...-->
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{tid}
</select>
</mapper>
package com.geek;
import com.geek.dao.ITeacherMapper;
import com.geek.pojo.Teacher;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest {
@Test
public void test02() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
ITeacherMapper mapper = sqlSession.getMapper(ITeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println("teacher = " + teacher);
// teacher = Teacher(id=0, name=李老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
sqlSession.close();
}
}
小结。
关联。association【多对一】。
集合。collection【一对多】。
- javaType。
用来指定实体类中属性的类型。- offType。
用来指定映射到 List 或者集合中的 pojo 类型。(泛型中的约束类型)。
结果映射(resultMap)。
constructor + 用于在实例化类时,注入结果到构造方法中
idArg + ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
arg + 将被注入到构造方法的一个普通结果
id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
result – 注入到字段或 JavaBean 属性的普通结果
association – 一个复杂类型的关联;许多结果将包装成这种类型
嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用
collection – 一个复杂类型的集合
嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用
discriminator – 使用结果值来决定使用哪个 resultMap
case – 基于某些值的结果映射
嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射。
ResultMap 的属性列表。
属性 | 描述 |
---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
动态 sql。
https://mybatis.org/mybatis-3/zh/dynamic-sql.html
动态 sql:根据不同的条件生成不同的 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。
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客 id。',
`title` varchar(100) NOT NULL COMMENT '博客标题。',
`author` varchar(30) NOT NULL COMMENT '博客作者。',
`create_time` datetime NOT NULL COMMENT '创建时间。',
`views` int(30) NOT NULL COMMENT '浏览量。'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
package com.geek.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class Blog {
private Stringid;
private String title;
private String author;
private Date createTime;
private int views;
}
- UUID 工具。
package com.geek.utils;
import org.junit.Test;
import java.util.UUID;
public class IDUtils {
public static String getId() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
@Test
public void test() {
System.out.println(IDUtils.getId());
}
}
<settings>
<!-- 标准日志实现。-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
if。
这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<insert id="addBlog" parameterType="blog">
insert into mybatis.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
</mapper>
package com.geek;
import com.geek.dao.IBlogMapper;
import com.geek.pojo.Blog;
import com.geek.utils.IDUtils;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
@Test
public void testQueryIF() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Map map = new HashMap();
map.put("title", "Java 如此简单");
List<Blog> blogs = mapper.queryBlogIF(map);
for (Blog blog : blogs) {
System.out.println("blog = " + blog);
}
}
}
where。
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<select id="queryBlogWhere" parameterType="map" resultType="blog">
select * from mybatis.blog -- where
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
</mapper>
package com.geek;
import com.geek.dao.IBlogMapper;
import com.geek.pojo.Blog;
import com.geek.utils.IDUtils;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
@Test
public void testQueryWhere() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Map map = new HashMap();
map.put("title", "Java 如此简单");
map.put("author", "Geek");
List<Blog> blogs = mapper.queryBlogWhere(map);
// ==> Preparing: select * from mybatis.blog WHERE title = ? and author = ?
for (Blog blog : blogs) {
System.out.println("blog = " + blog);
}
}
}
choose, when, otherwise。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<select id="queryBlogWhere" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
</mapper>
package com.geek;
import com.geek.dao.IBlogMapper;
import com.geek.pojo.Blog;
import com.geek.utils.IDUtils;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
@Test
public void testQueryChoose() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Map map = new HashMap();
// map.put("title", "Java 如此简单");
// map.put("author", "Geek");
map.put("views", 9999);
// blog = Blog(id=49f3dba610704b269bc91b5200025e4e, title=Java 如此简单, author=Geek, createTime=Sat May 30 15:52:02 CST 2020, views=9999)
//blog = Blog(id=b4b7b18872714e0bb8f78b830a99c65b, title=Spring 如此简单, author=Geek, createTime=Sat May 30 18:03:26 CST 2020, views=9999)
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println("blog = " + blog);
}
}
}
如果都满足 if 的 test,where 语句只选第一个。(像 Java 的 switch)。
trim、where、set。
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
</mapper>
package com.geek;
import com.geek.dao.IBlogMapper;
import com.geek.pojo.Blog;
import com.geek.utils.IDUtils;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MyTest {
@Test
public void testUpdateSet() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Map map = new HashMap();
map.put("title", "Python 如此简单");
map.put("author", "Geek");
map.put("id", "1");
int i = mapper.updateBlog(map);
System.out.println("i = " + i);
}
}
trim。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
foreach。
sql 片段~include。
- 使用 sql 标签抽取公共的部分。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<sql id="if-title-author">
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</sql>
在需要使用的地方使用 include 标签引用即可。
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
</mapper>
foreach。
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IBlogMapper">
<!-- select * from mybatis.blog where 1 = 1 and (id = 1 or id = 2 or id = 3)-->
<!-- 传递一个万能的 map,这个 map 中存在一个集合。-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
</mapper>
package com.geek.dao;
import com.geek.pojo.Blog;
import java.util.List;
import java.util.Map;
public interface IBlogMapper {
// 查询第 1 2 3 号博客。
List<Blog> queryBlogForeach(Map map);
}
package com.geek;
import com.geek.dao.IBlogMapper;
import com.geek.pojo.Blog;
import com.geek.utils.IDUtils;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;
public class MyTest {
@Test
public void testForeach() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Map map = new HashMap();
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids", ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
blogs.forEach(System.out::println);
sqlSession.close();
}
}
~~~
==> Preparing: select * from mybatis.blog WHERE ( id = ? or id = ? or id = ? )
==> Parameters: 1(Integer), 2(Integer), 3(Integer)
缓存。
what is cache.
- 存在内存中的临时数据。
- 讲用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
MyBatis 缓存。
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
基本上就是这样。这个简单语句的效果如下。
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
提示
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用@CacheNamespaceRef
注解指定缓存作用域。
-
MyBatis 系统中定义了两级缓存:一级缓存和二级缓存。
默认情况下只有一级缓存开启。(SqlSession 级别的缓存,也称为本地缓存
)。 -
二级缓存需要手动开启,ta 是基于 namespace 级别的缓存。
-
为了提高扩展性,MyBatis 定义了缓存接口 Cache,我们可以通过实现 Cache 接口来自定义二级缓存。
一级缓存。
也叫本地缓存。
与数据库同一会话期间查询到的数据会放在本地缓存中。
以后如果需要获得相同的数据,直接从缓存中拿,没必要再去查询数据库。
package com.geek;
import com.geek.dao.IUserMapper;
import com.geek.pojo.User;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest {
@Test
public void test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User user = mapper.queryUserById(1);
System.out.println("user = " + user);
System.out.println("~ ~ ~ ~ ~ ~ ~");
User user1 = mapper.queryUserById(1);
System.out.println("user1 = " + user1);
System.out.println(user == user1);
sqlSession.close();
}
}
两次查询(查询一样的数据),sql 只执行了一次。
缓存失效的情况。
-
查询不同的数据。
-
增删改操作,可能会改变原来的数据,所以必定会刷新缓存。
-
查询不同的 Mapper.xml。
-
手动清理缓存。sqlSession.clearCache();。
一级缓存就是一个 map。
二级缓存。
提示
缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
这些属性可以通过 cache 元素的属性来修改。比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示
二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
开启二级缓存。
设置(settings)。
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true / false | true |
<settings>
<!-- 标准日志实现。-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 显式的开启全局缓存。默认也是开启。-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 开启。
<cache/>
也可以在某一条指定的 sql 上开启二级缓存。useCache="true"
。
<?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 绑定一个对应的 Dao/Mapper 接口。-->
<mapper namespace="com.geek.dao.IUserMapper">
<!-- 在当前 Mapper.xml 中使用二级缓存。-->
<cache/>
<select id="queryUserById" resultType="user" useCache="true">
select * from user where id = #{id}
</select>
</mapper>
或 + 自定义参数。
<!-- 在当前 Mapper.xml 中使用二级缓存。-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
基于namespace 级别的缓存,一个名称空间,对应一个一级缓存。
工作机制。
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。
- 如果当前会话关闭了,这个会话对应的一级缓存就没了。但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中,
- 新的会话查询信息,就可以从二级缓存中获取内容。
- 不同的 mapper 查出的数据会放在自己对应的缓存(map)中。
二级缓存测试。
两个 SqlSession(会话),一个 SqlSession 关闭,这个一级缓存就被放入二级缓存。第二个 SqlSession 就使用二级缓存。
package com.geek;
import com.geek.dao.IUserMapper;
import com.geek.pojo.User;
import com.geek.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest {
@Test
public void test() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
User user = mapper.queryUserById(1);
System.out.println("user = " + user);
sqlSession.close();
/*
> - 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中。
> - 如果当前会话关闭了,这个会话对应的一级缓存就没了。但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中,
> - 新的会话查询信息,就可以从二级缓存中获取内容。
> - 不同的 mapper 查出的数据会放在自己对应的缓存(map)中。
*/
System.out.println("~ ~ ~ ~ ~ ~ ~");
// sqlSession.clearCache();// 手动清理缓存。
User user2 = mapper2.queryUserById(1);
System.out.println("user2 = " + user2);
System.out.println(user == user2);
sqlSession2.close();
}
}
问题。
如果只使用 <cache />
开启二级缓存,实体类需要序列化。
org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.geek.pojo.User
所有数据传统先放在一级缓存中。
只有当会话提交或关闭,才会进入二级缓存。
二级缓存接口。
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
/**
* SPI for cache providers.
* <p>
* One instance of cache will be created for each namespace.
* <p>
* The cache implementation must have a constructor that receives the cache id as an String parameter.
* <p>
* MyBatis will pass the namespace as id to the constructor.
*
* <pre>
* public MyCache(final String id) {
* if (id == null) {
* throw new IllegalArgumentException("Cache instances require an ID");
* }
* this.id = id;
* initialize();
* }
* </pre>
*
* @author Clinton Begin
*/
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* As of 3.3.0 this method is only called during a rollback
* for any previous value that was missing in the cache.
* This lets any blocking cache to release the lock that
* may have previously put on the key.
* A blocking cache puts a lock when a value is null
* and releases it when the value is back again.
* This way other threads will wait for the value to be
* available instead of hitting the database.
*
*
* @param key The key
* @return Not used
*/
Object removeObject(Object key);
/**
* Clears this cache instance.
*/
void clear();
/**
* Optional. This method is not called by the core.
*
* @return The number of elements stored in the cache (not its capacity).
*/
int getSize();
/**
* Optional. As of 3.2.6 this method is no longer called by the core.
* <p>
* Any locking needed by the cache must be provided internally by the cache provider.
*
* @return A ReadWriteLock
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
缓存原理。
用户查询,先走二级缓存,再一级缓存。
ehcache。
EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。
Ehcache 是一种广泛使用的开源 Java 分布式缓存。主要面向通用缓存,Java EE 和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个 gzip 缓存 servlet 过滤器,支持 REST 和 SOAP api 等特点。
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
- mapper.xml 中配置 ehcache。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- Java 类 implements Cache。