66 min to read
MyBatis
MyBatis
- 第1章 MyBatis是什么
- 第2章 MyBatis的核心组件
- 第3章 第一个MyBatis程序
- 第4章 MyBatis配置文件详解
- 第5章 MyBatis与Spring的整合
- 第6章 映射器元素详解
- 第7章 MyBatis关联查询(级联查询)
- 第8章 动态SQL
第1章 MyBatis是什么
1.1 MyBatis概念
- MyBatis(前身是iBatis)是一个支持普通SQL查询、存储过程以及高级映射的持久层框架。
- MyBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
- MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。
- MyBatis 框架也被称为 ORM(Object/Relation Mapping,即对象关系映射)框架。
1.2 MyBatis工作原理
- 工作流程:
- 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
- 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
- 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
- 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
- Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
- MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
- 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
- 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
第2章 MyBatis的核心组件
- MyBatis 的核心组件分为 4 个部分: SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession 和 SQL Mapper。
SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。 SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。 SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。 SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。
2.1 SqlSessionFactoryBuilder(构造器)/SqlSessionFactory(工厂接口)
- 使用 MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,而 MyBatis 提供了构造器 SqlSessionFactoryBuilder。
- 在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory。
- SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory
2.1.1 使用 XML 构建 SqlSessionFactory(推荐)
- 在 MyBatis 中的 XML 分为两类,一类是基础配置文件,通常只有一个,主要是配置一些最基本的上下文参数和运行环境;另一类是映射文件,它可以配置映射关系、SQL、参数等信息。先看一份简易的基础配置文件,我们把它命名为 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>
<typeAliases><!--别名-->
<typeAliases alias="user" type="com.mybatis.po.User"/>
</typeAliases>
<!-- 数据库环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<!-- 连接数据库的URL -->
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mybatis/mapper/UserMapper.xml" />
</mappers>
</configuration>
元素定义了一个别名 user,它代表着 com.mybatis.po.User 这个类。这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。 元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。 元素配置数据库,其中属性 type="POOLED" 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。 元素代表引入的那些映射器,在谈到映射器时会详细讨论它。
- 有了基础配置文件,就可以用一段很简短的代码来生成 SqlSessionFactory 了,如下所示。
SqlSessionFactory factory = null;
String resource = "mybatis-config.xml";
InputStream is;
try {
InputStream is = Resources.getResourceAsStream(resource);
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
首先读取 mybatis-config.xml,然后通过 SqlSessionFactoryBuilder 的 Builder 方法去创建 SqlSessionFactory。
2.1.2 使用代码创建 SqlSessionFactory(不推荐)
- 通过代码来实现与使用 XML 构建 SqlSessionFactory 一样的功能——创建 SqlSessionFactory,代码如下所示。
// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("root");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;
2.2 SqlSession(会话)
- 在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。
- DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:
- 获取 Mapper 接口。
- 发送 SQL 给数据库。
- 控制数据库事务。
- 有了 SqlSessionFactory 创建的 SqlSession 就十分简单了,如下所示。
SqlSession sqlSession = SqlSessionFactory.openSession();
- SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL。它就好像一家软件公司的商务人员,是一个门面,而实际干活的是软件工程师。在 MyBatis 中,真正干活的是 Executor,我们会在底层看到它。
- SqlSession 控制数据库事务的方法,如下所示。
//定义 SqlSession
SqlSession sqlSession = null;
try {
// 打开 SqlSession 会话
sqlSession = SqlSessionFactory.openSession();
// some code...
sqlSession.commit(); // 提交事务
} catch (IOException e) {
sqlSession.rollback(); // 回滚事务
}finally{
// 在 finally 语句中确保资源被顺利关闭
if(sqlSession != null){
sqlSession.close();
}
}
使用 commit 方法提交事务,或者使用 rollback 方法回滚事务。因为它代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,那么数据库的连接资源就会很快被耗费光,整个系统就会陷入瘫痪状态,所以用 finally 语句保证其顺利关闭。
2.3 Mapper(映射器)
- 映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。它可以配置以下内容:
- 描述映射规则。
- 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
- 配置缓存。
- 提供动态 SQL。
- 有两种实现映射器的方式,XML 文件形式和注解形式。不过在此之前,先用以下 SQL 语句创建 role 表。
CREATE TABLE `role` (
`id` bigint(20) NOT NULL,
`role_name` varchar(20) DEFAULT NULL,
`note` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 并且定义一个 POJO,它十分简单,如下所示。
package com.mybatis.pojo;
public class Role {
private Long id;
private String roleName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
- 映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。
2.3.1 用 XML 实现映射器(推荐)
- 用 XML 定义映射器分为两个部分:接口和 XML。先定义一个映射器接口,如下所示。
package com.mybatis.mapper;
import com.mybatis.pojo.Role;
public interface RoleMapper {
public Role getRole(Long id);
}
- 在用 XML 方式创建 SqlSession 的配置文件中有这样一段代码:
<mapper resource="com/mybatis/mapper/RoleMapper.xml" />
它的作用就是引入一个 XML 文件。
- 用 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="com.mybatis.mapper.RoleMapper">
<select id="getRole" parameterType="long" resultType="role">
SELECT id,role_name as roleName,note FROM role WHERE id =#{id}
</select>
</mapper>
元素中的属性 namespace 所对应的是一个接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。
这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。
这里 SQL 返回的列名 id 和 note 是可以和之前定义的 POJO 的属性对应起来的,而表里的列 role_name 通过 SQL 别名的改写,使其成为 roleName,也是和 POJO 对应起来的,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。
2.3.2 注解实现映射器(不推荐)
- 除 XML 方式定义映射器外,还可以采用注解方式定义映射器,它只需要一个接口就可以通过 MyBatis 的注解来注入 SQL,如下所示。
package com.mybatis.mapper;
import org.apache.ibatis.annotations.Select;
import com.mybatis.pojo.Role;
public interface RoleMapper2 {
@Select("select id,role_name as roleName,note from t_role where id=#{id}")
public Role getRole(Long id);
}
2.4 MyBatis执行SQL的两种方式
2.4.1 SqlSession 发送 SQL(不推荐)
- 有了映射器就可以通过 SqlSession 发送 SQL 了。我们以 getRole 这条 SQL 为例看看如何发送 SQL。
Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);
/**
* String 对象是由一个命名空间加上 SQL id 组合而成的,
* 它完全定位了一条 SQL,
* 这样 MyBatis 就会找到对应的 SQL。
* 如果在 MyBatis 中只有一个 id 为 getRole 的 SQL,
* 那么也可以简写为:
*/
Role role = (Role)sqlSession.selectOne("getRole",1L);
selectOne 方法表示使用查询并且只返回一个对象,而参数则是一个 String 对象和一个 Object 对象。这里是一个 long 参数,long 参数是它的主键。
2.4.2 用 Mapper 接口发送 SQL(推荐)
- SqlSession 还可以获取 Mapper 接口,通过 Mapper 接口发送 SQL,如下所示。
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果。
2.4.3 对比两种发送 SQL 方式
- 上面分别展示了 MyBatis 存在的两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。建议采用 SqlSession 获取 Mapper 的方式,理由如下:
- 使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。使用 Mapper 接口,类似 roleMapper.getRole(1L)则是完全面向对象的语言,更能体现业务的逻辑。
- 使用 Mapper.getRole(1L)方式,IDE 会提示错误和校验,而使用 sqlSession.selectOne(“getRole”,1L)语法,只有在运行中才能知道是否会产生错误。
- 目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单。
2.5 核心组件的生命周期
- 生命周期是组件的重要问题,尤其是在多线程的环境中,比如互联网应用、Socket 请求等,而 MyBatis 也常用于多线程的环境中,错误使用会造成严重的多线程并发问题,为了正确编写 MyBatis 的应用程序,我们需要掌握 MyBatis 组件的生命周期。
- 所谓生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被 Java 虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命周期。
2.5.1 SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
- MyBatis 组件的生命周期:
2.5.2 SqlSessionFactory
- SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
2.5.3 SqlSession
- 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
2.5.4 Mapper
- Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。
第3章 第一个MyBatis程序
- 在创建项目之前,首先在 MySQL 数据库中创建 mybatis 数据库和 user 表,sql 语句如下所示:
CREATE DATABASE mybatis;
USE mybatis;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` tinyint(2) NOT NULL,
`uname` varchar(20) DEFAULT NULL,
`usex` varchar(10) DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.1 具体步骤
1)创建 Web 应用,并添加相关 JAR 包
- 在 IDEA 中创建一个名为 myBatisDemo01 的 Web 应用,将 MyBatis 的核心 JAR 包、依赖 JAR 包以及 MySQL 数据库的驱动 JAR 包一起复制到 /WEB-INF/lib 目录下。
ant-1.10.3.jar
ant-launcher-1.10.3.jar
asm-7.0.jar
cglib-3.2.10.jar
commons-logging-1.2.jar
javassist-3.24.1-GA.jar
log4j-1.2.17.jar
log4j-api-2.11.2.jar
log4j-core-2.11.2.jar
mybatis-3.5.2.jar
mysql-connector-java-8.0.20.jar
ognl-3.2.10.jar
slf4j-api-1.7.26.jar
slf4j-log4j12-1.7.26.jar
2)创建日志文件
- MyBatis 默认使用 log4j 输出日志信息,如果开发者需要查看控制台输出的 SQL 语句,那么需要在 classpath 路径下配置其日志文件。在 myBatis 应用的 src 目录下创建 log4j.properties 文件,其内容如下:
# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
在日志文件中配置了全局的日志配置、MyBatis 的日志配置和控制台输出,其中 MyBatis 的日志配置用于将 com.mybatis 包下所有类的日志记录级别设置为 DEBUG。该配置文件内容不需要开发者全部手写,可以从 MyBatis 使用手册中的 Logging 小节复制,然后进行简单修改。
3)创建持久化类
- 在 src 目录下创建一个名为 com.mybatis.po 的包,在该包中创建持久化类 MyUser,注意在类中声明的属性与数据表 user 的字段一致。
package com.mybatis.po;
/**
* springtest数据库中user表的持久类
*/
public class MyUser {
private Integer uid; // 主键
private String uname;
private String usex;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUsex() {
return usex;
}
public void setUsex(String usex) {
this.usex = usex;
}
// 此处省略setter和getter方法
@Override
public String toString() { // 为了方便查看结果,重写了toString方法
return "User[uid=" + uid + ",uname=" + uname + ",usex=" + usex + "]";
}
}
4)创建映射文件
- 在 src 目录下创建一个名为 com.mybatis.mapper 的包,在该包中创建映射文件 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">
<mapper namespace="com.mybatis.mapper.UserMapper">
<!-- 根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer"
resultType="com.mybatis.po.MyUser">
select * from user where uid = #{uid}
</select>
<!-- 查询所有用户信息 -->
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
select * from user
</select>
<!-- 添加一个用户,#{uname}为 com.mybatis.po.MyUser 的属性值 -->
<insert id="addUser" parameterType="com.mybatis.po.MyUser">
insert into user (uid,uname,usex)
values(#{uid},#{uname},#{usex})
</insert>
<!--修改一个用户 -->
<update id="updateUser" parameterType="com.mybatis.po.MyUser">
update user set uname =
#{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid
= #{uid}
</delete>
</mapper>
- 在上述映射文件中,
元素是配置文件的根元素,它包含了一个 namespace 属性,该属性值通常设置为“包名+SQL映射文件名”,指定了唯一的命名空间。
子元素 <select>、<insert>、<update> 以及 <delete> 中的信息是用于执行查询、添加、修改以及删除操作的配置。在定义的 SQL 语句中,“#{}”表示一个占位符,相当于“?”,而“#{uid}”表示该占位符待接收参数的名称为 uid。
5)创建 MyBatis 的配置文件
- 在 src 目录下创建 MyBatis 的核心配置文件 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>
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<!-- 配置mybatis运行环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!-- 连接数据库的URL -->
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mybatis/mapper/UserMapper.xml" />
</mappers>
</configuration>
6)创建测试类
- 在 src 目录下创建一个名为 com.mybatis.test 的包,在该包中创建 MyBatisTest 测试类。在测试类中首先使用输入流读取配置文件,然后根据配置信息构建 SqlSessionFactory 对象。 接下来通过 SqlSessionFactory 对象创建 SqlSession 对象,并使用 SqlSession 对象的方法执行数据库操作。
package com.mybatis.test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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 com.mybatis.po.MyUser;
public class MyBatisTest {
public static void main(String[] args) {
try {
// 读取配置文件 mybatis-config.xml
InputStream config = Resources
.getResourceAsStream("mybatis-config.xml");
// 根据配置文件构建SqlSessionFactory
SqlSessionFactory ssf = new SqlSessionFactoryBuilder()
.build(config);
// 通过 SqlSessionFactory 创建 SqlSession
SqlSession ss = ssf.openSession();
// SqlSession执行映射文件中定义的SQL,并返回映射结果
/*
* com.mybatis.mapper.UserMapper.selectUserById 为 UserMapper.xml
* 中的命名空间+select 的 id
*/
// 查询一个用户
MyUser mu = ss.selectOne(
"com.mybatis.mapper.UserMapper.selectUserById", 1);
System.out.println(mu);
// 添加一个用户
MyUser addmu = new MyUser();
addmu.setUid(2);
addmu.setUname("陈恒");
addmu.setUsex("男");
ss.insert("com.mybatis.mapper.UserMapper.addUser", addmu);
// 修改一个用户
MyUser updatemu = new MyUser();
updatemu.setUid(1);
updatemu.setUname("张三");
updatemu.setUsex("女");
ss.update("com.mybatis.mapper.UserMapper.updateUser", updatemu);
// 删除一个用户
ss.delete("com.mybatis.mapper.UserMapper.deleteUser", 3);
// 查询所有用户
List<MyUser> listMu = ss
.selectList("com.mybatis.mapper.UserMapper.selectAllUser");
for (MyUser myUser : listMu) {
System.out.println(myUser);
}
// 提交事务
ss.commit();
// 关闭 SqlSession
ss.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
7)运行程序查看结果
DEBUG [main] - ==> Preparing: select * from user where uid = ?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
User[uid=1,uname=张三,usex=女]
DEBUG [main] - ==> Preparing: insert into user (uid,uname,usex) values(?,?,?)
DEBUG [main] - ==> Parameters: 2(Integer), 陈恒(String), 男(String)
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: update user set uname = ?,usex = ? where uid = ?
DEBUG [main] - ==> Parameters: 张三(String), 女(String), 1(Integer)
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: delete from user where uid = ?
DEBUG [main] - ==> Parameters: 3(Integer)
DEBUG [main] - <== Updates: 0
DEBUG [main] - ==> Preparing: select * from user
DEBUG [main] - ==> Parameters:
DEBUG [main] - <== Total: 2
User[uid=1,uname=张三,usex=女]
User[uid=2,uname=陈恒,usex=男]
第4章 MyBatis配置文件详解
- MyBatis 配置文件并不复杂,它所有的元素如下所示。
<?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 /><!-- 属性 -->
<settings /><!-- 设置 -->
<typeAliases /><!-- 类型命名 -->
<typeHandlers /><!-- 类型处理器 -->
<objectFactory /><!-- 对象工厂 -->
<plugins /><!-- 插件 -->
<environments><!-- 配置环境 -->
<environment><!-- 环境变量 -->
<transactionManager /><!-- 事务管理器 -->
<dataSource /><!-- 数据源 -->
</environment>
</environments>
<databaseIdProvider /><!-- 数据库厂商标识 -->
<mappers /><!-- 映射器 -->
</configuration>
- MyBatis 配置项的顺序不能颠倒。如果颠倒了它们的顺序,那么在 MyBatis 启动阶段就会发生异常,导致程序无法运行。
4.1 properties(属性)元素
- properties 属性可以给系统配置一些运行参数,可以放在 XML 文件或者 properties 文件中,而不是放在 Java 编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis 提供了 3 种方式让我们使用 properties,它们是:
- property 子元素。
- properties 文件。
- 程序代码传递。
4.1.1 property 子元素
- 以下面代码为基础,使用 property 子元素将数据库连接的相关配置进行改写,如下所示。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J" />
</settings>
<!-- 配置mybatis运行环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC的事务管理 -->
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<!-- MySQL数据库驱动 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<!-- 连接数据库的URL -->
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="com/mybatis/mapper/UserMapper.xml" />
</mappers>
</configuration>
这里使用了元素
下的子元素 定义,用字符串 database.username 定义数据库用户名,然后就可以在数据库定义中引入这个已经定义好的属性参数,如 ${database.username},这样定义一次就可以到处引用了。但是如果属性参数有成百上千个,显然使用这样的方式不是一个很好的选择,这个时候可以使用 properties 文件。
4.1.2 使用 properties 文件
- 使用 properties 文件是比较普遍的方法,一方面这个文件十分简单,其逻辑就是键值对应,我们可以配置多个键值放在一个 properties 文件中,也可以把多个键值放到多个 properties 文件中,这些都是允许的,它方便日后维护和修改。
- 创建一个文件 jdbc.properties 放到 classpath 的路径下,如下所示。
database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
database.username=root
database.password=root
- 在 MyBatis 中通过
的属性 resource 来引入 properties 文件。
<properties resource="jdbc.properties"/>
4.1.3 使用程序传递方式传递参数
- 在真实的生产环境中,数据库的用户密码是对开发人员和其他人员保密的。运维人员为了保密,一般都需要把用户和密码经过加密成为密文后,配置到 properties 文件中。
- 对于开发人员及其他人员而言,就不知道其真实的用户密码了,数据库也不可能使用已经加密的字符串去连接,此时往往需要通过解密才能得到真实的用户和密码了。
- 假设系统已经为提供了这样的一个 CodeUtils.decode(str)进行解密,那么我们在创建 SqlSessionFactory 前,就需要把用户名和密码解密,然后把解密后的字符串重置到 properties 属性中,如下所示。
String resource = "mybatis-config.xml";
InputStream inputStream;
Inputstream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.password");
//解密用户和密码,并在属性中重置
props.put("database.username", CodeUtils.decode(username));
props.put ("database.password", CodeUtils.decode(password));
inputstream = Resources.getResourceAsStream(resource);
//使用程序传递的方式覆盖原有的properties属性参数
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream, props);
- 首先使用 Resources 对象读取了一个 jdbc.properties 配置文件,然后获取了它原来配置的用户和密码,进行解密并重置,最后使用 SqlSessionFactoryBuilder 的 build 方法,传递多个 properties 参数来完成。这将覆盖之前配置的密文,这样就能连接数据库了,同时也满足了运维人员对数据库用户和密码安全的要求。
4.2 settings(设置)元素
- 在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。
4.3 typeAliases(别名)元素
- 由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。
- 在 MyBatis 中别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。在 MyBatis 中别名不区分大小写。
4.3.1 系统定义别名
别名 | Java类型 | 是否支持数组 |
---|---|---|
_byte | byte | 是 |
_long | long | 是 |
_short | short | 是 |
_int | int | 是 |
_integer | int | 是 |
_double | double | 是 |
_float | float | 是 |
_boolean | boolean | 是 |
string | String | 是 |
byte | Byte | 是 |
long | Long | 是 |
short | Short | 是 |
int | Integer | 是 |
integer | Integer | 是 |
double | Double | 是 |
float | Float | 是 |
boolean | Boolean | 是 |
date | Date | 是 |
decimal | BigDecimal | 是 |
bigdecimal | BigDecimal | 是 |
object | Object | 是 |
map | Map | 否 |
hashmap | HashMap | 否 |
list | List | 否 |
arraylist | ArrayList | 否 |
collection | Collection | 否 |
iterator | Iterator | 否 |
ResultSet | ResultSet | 否 |
-
如果需要使用对应类型的数组型,要看其是否能支持数据,如果支持只需要使用别名加 [] 即可,比如 _int 数组的别名就是 _int[]。
-
有时候要通过代码来实现注册别名,如下所示。
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
......
registerAlias("byte[]",Byte[].class); registerAlias("long[]",Long[].class);
......
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
- 所以使用 TypeAliasRegistry 的 registerAlias 方法就可以注册别名了。一般是通过 Configuration 获取 TypeAliasRegistry 类对象,其中有一个 getTypeAliasRegistry 方法可以获得别名,如 configuration.getTypeAliasRegistry()。
- 然后就可以通过 registerAlias 方法对别名注册了。而事实上 Configuration 对象也对一些常用的配置项配置了别名,如下所示。
//事务方式别名
typeAliasRegistry.registerAlias("JDBC",JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class);
//数据源类型别名
typeAliasRegistry.registerAlias("JNDI",JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED",
PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED",UnpooledDataSourceFactory.class);
//缓存策略别名
typeAliasRegistry.registerAlias("PERPETUAL",PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO",FifoCache.class);
typeAliasRegistry.registerAlias("LRU",LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
//数据库标识别名
typeAliasRegistry.registerAlias("DB_VENDOR",
VendorDatabaseIdProvider.class);
//语言驱动类别名
typeAliasRegistry.registerAlias("XML",XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW",RawLanguageDriver.class);
//日志类别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGTNG",JakartmCommonsLogginglmpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING",NoLoggingImpl.class);
//动态代理别名
typeAliasRegistry.registerAlias("CGLIB",CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST",JavassistProxyFactory.class);
4.3.2 自定义别名
- 由于现实中,特别是大型互联网系统中存在许多对象,比如用户(User)这个对象有时候需要大量重复地使用,因此 MyBatis 也提供了用户自定义别名的规则。我们可以通过 TypeAliasRegistry 类的 registerAlias 方法注册,也可以采用配置文件或者扫描方式来自定义它。
- 使用配置文件定义很简单:
<typeAliases><!--别名-->
<typeAlias alias="role" type="com.mybatis.po.Role"/>
<typeAlias alias="user" type="com.mybatis.po.User"/>
</typeAliases>
- 这样就可以定义一个别名了。如果有很多类需要定义别名,那么用这样的方式进行配置可就不那么轻松了。MyBatis 还支持扫描别名。比如上面的两个类都在包 com.mybatis.po 之下,那么就可以定义为:
<typeAliases><!--别名-->
<package name="com.mybatis.po"/>
</typeAliases>
- 这样 MyBatis 将扫描这个包里面的类,将其第一个字母变为小写作为其别名,比如类 Role 的别名会变为 role,而 User 的别名会变为 user。使用这样的规则,有时候会出现重名。
- 比如 com.mybatis.po.User 这个类,MyBatis 还增加了对包 com.mybatis.po 的扫描,那么就会出现异常,这个时候可以使用 MyBatis 提供的注解 @Alias(”user3”)进行区分,如下所示。
package com.mybatis.po;
@Alias("user3")
public Class User {
......
}
4.4 TypeHandler(类型转换器)元素
- 在 JDBC 中,需要在 PreparedStatement 对象中设置那些已经预编译过的 SQL 语句的参数。执行 SQL 后,会通过 ResultSet 对象获取得到数据库的数据,而这些 MyBatis 是根据数据的类型通过 typeHandler 来实现的。
- 在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,typeHandler的作用就是将预处理语句中传入的参数从javaType转换为jdbcType,或者从数据库取出结果时将jdbcType转换为javaType。
- typeHandler的作用:
-
和别名一样,在 MyBatis 中存在系统定义 typeHandler 和自定义 typeHandler。MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。
-
注册一个类的类型处理器:
<typeHandlers>
<typeHandler handler="com.mybatis.typehandler.CustomtypeHandler" />
</typeHandlers>
- 注册一个包中所有的类型处理器:
<typeHandlers>
<package name="com.mybatis.typehandler" />
</typeHandlers>
4.5 objectFactory(对象工厂)元素
- MyBatis 中默认的 ObjectFactory 的作用是实例化目标类,它既可以通过默认构造方法实例化,也可以在参数映射存在的时候通过参数构造方法来实例化。通常使用默认的 ObjectFactory 即可。
- 大部分场景下都不用配置和修改默认的ObjectFactory ,如果想覆盖ObjectFactory的默认行为,可以通过自定义 ObjectFactory 来实现。
4.6 environments(环境)元素
- 在 MyBatis 中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言只需要配置其中的一个就可以了。
- 它下面又分为两个可配置的元素:事务管理器(transactionManager)、数据源(dataSource)。
- 在实际的工作中,大部分情况下会采用 Spring 对数据源和数据库的事务进行管理。
- 运行环境配置,代码如下所示:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${database.driver}" />
<property name="url"
value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</dataSource>
</environment>
</environments>
4.6.1 transactionManager(事务管理器)
- 在 MyBatis 中,可以配置两种类型的事务管理器,分别是 JDBC 和 MANAGED 。
JDBC:此配置直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务的作用域。
MANAGED:此配置从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。默认情况下,它会关闭连接,但一些容器并不希望这样可设置closeConnection=false。
4.6.2 dataSourc(数据源)
1)UNPOOLED(不推荐)
- UNPOOLED 采用非数据库池的管理方式,每次请求都会打开一个新的数据库连接,所以创建会比较慢。在一些对性能没有很高要求的场合可以使用它。
- 对有些数据库而言,使用连接池并不重要,那么它也是一个比较理想的选择。UNPOOLED 类型的数据源可以配置以下几种属性:
- driver 数据库驱动名,比如 MySQL 的 com.mysql.jdbc.Driver。
- url 连接数据库的 URL。
- username 用户名。
- password 密码。
- defaultTransactionIsolationLevel 默认的连接事务隔离级别。
2)POOLED(推荐)
- 数据源 POOLED 利用“池”的概念将 JDBC 的 Connection 对象组织起来,它开始会有一些空置,并且已经连接好的数据库连接,所以请求时,无须再建立和验证,省去了创建新的连接实例时所必需的初始化和认证时间。它还控制最大连接数,避免过多的连接导致系统瓶颈。
- 配置 POOLED 的数据源属性说明:
名称 | 说明 |
---|---|
poolMaximumActiveConnections |
是在任意时间都存在的活动(也就是正在使用)连接数量, 默认值为 10 |
poolMaximumIdleConnections | 是任意时间可能存在的空闲连接数 |
poolMaximumCheckoutTime |
在被强制返回之前,池中连接被检出(checked out)的时间, 默认值为 20 000 毫秒(即 20 秒) |
poolTimeToWait |
是一个底层设置, 如果获取连接花费相当长的时间, 它会给连接池打印状态日志, 并重新尝试获取一个连接(避免在误配置的情况下一直失败), 默认值为 20 000 毫秒(即 20 秒)。 |
poolPingQuery |
为发送到数据库的侦测查询, 用来检验连接是否处在正常工作秩序中, 并准备接受请求。 默认是“NO PING QUERY SET”, 这会导致多数数据库驱动失败时带有一个恰当的错误消息。 |
poolPingEnabled |
为是否启用侦测查询。 若开启, 也必须使用一个可执行的 SQL 语句设置 poolPingQuery 属性(最好是一个非常快的 SQL), 默认值为 false。 |
poolPingConnectionsNotUsedFor |
为配置 poolPingQuery 的使用频度。 这可以被设置成匹配具体的数据库连接超时时间, 来避免不必要的侦测, 默认值为 0(即所有连接每一时刻都被侦测——仅当 poolPingEnabled 为 true 时适用)。 |
3)JNDI(不推荐)
- 数据源 JNDI 的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。这种数据源配置只需要两个属性:
- initial_context 用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。initial_context 是个可选属性,如果忽略,那么 data_source 属性将会直接从 InitialContext 中寻找。
- data_source 是引用数据源实例位置上下文的路径。当提供 initial_context 配置时,data_source 会在其返回的上下文中进行查找;当没有提供 initial_context 时,data_source 直接在 InitialContext 中查找。
第5章 MyBatis与Spring的整合
5.1 整合实例
1)创建应用并导入相关 JAR 包
- 创建一个名为 MyBatis-Spring 的 Web 应用,并将相关jar包导入 /WEB-INF/lib 目录下。
commons-logging-1.2.jar
commons-dbcp2-2.1.1.jar
commons-pool2-2.4.2.jar
commons-pool2-2.8.1.jar
druid-1.1.9.jar
aspectjweaver-1.5.0.jar
log4j-1.2.17.jar
mybatis-3.5.2.jar
mybatis-spring-1.3.1.jar
mysql-connector-java-8.0.20.jar
spring-aop-4.3.6.RELEASE.jar
spring-beans-4.3.6.RELEASE.jar
spring-context-4.3.6.RELEASE.jar
spring-aspects-4.3.6.RELEASE.jar
spring-core-4.3.6.RELEASE.jar
spring-jdbc-4.1.2.RELEASE.jar
spring-expression-4.3.6.RELEASE.jar
spring-jdbc-4.3.2.RELEASE.jar
spring-test-3.2.13.RELEASE.jar
spring-test-4.3.2.RELEASE.jar
spring-tx-4.3.6.RELEASE.jar
2)创建持久化类
- 在 src 目录下创建一个名为 com.pojo 的包,并创建 User 类。
package com.pojo;
public class User {
int uid;
String uname;
String usex;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUsex() {
return usex;
}
public void setUsex(String usex) {
this.usex = usex;
}
@Override
public String toString() {
return (uid + " " + uname + " " + usex);
}
}
3)创建 SQL 映射文件和 数据访问接口
-
在 src 目录下创建一个名为 com.mapper 的包,在该包中创建 UserMapper 接口和 SQL 映射文件 UserMapper.xml。
-
UserMapper接口:
package com.mapper;
import com.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 接口方法对应的SQL映射文件UserMapper.xml中的id
*/
public User selectUserById(Integer uid);
public List<User> selectAllUser();
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(Integer uid);
}
- 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">
<mapper namespace="com.mapper.UserMapper">
<select id="selectUserById" parameterType="Integer" resultType="com.pojo.User">
select * from user where uid = #{uid}
</select>
<!-- 查询所有用户信息 -->
<select id="selectAllUser" resultType="com.pojo.User">
select * from user
</select>
<!-- 添加一个用户,#{uname}为 com.mybatis.po.MyUser 的属性值 -->
<insert id="addUser" parameterType="com.pojo.User">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
<!--修改一个用户 -->
<update id="updateUser" parameterType="com.pojo.User">
update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid = #{uid}
</delete>
</mapper>
4)创建 MyBatis 核心配置文件
- 在 src 中创建 MyBatis 核心配置文件 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>
<typeAliases>
<typeAlias alias="User" type="com.pojo.User"/>
</typeAliases>
</configuration>
5)创建 Spring 核心配置文件以及数据库资源文件
- 在 src 中创建 MyBatis 核心配置文件 applicationContext.xml 和数据库资源文件 db.properties 。
- applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!--配置数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<!-- 加载驱动 -->
<property name="driverClassName" value="${jdbc.driver}"></property>
<!-- 数据库的名字 -->
<property name="url" value="${jdbc.url}"></property>
<!-- 用户名密码 -->
<property name="username" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<!-- 最大连接数 -->
<property name="maxTotal" value="30"/>
<!-- 最大空闲连接数 -->
<property name="maxIdle" value="10"/>
<!-- 初始化连接数 -->
<property name="initialSize" value="5"/>
</bean>
<!-- 配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--配置userMapper对象-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
- db.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
jdbc.user=root
jdbc.password=root
6)创建日志文件
- 在 src 目录下创建日志文件 log4j.properties,文件内容如下:
# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
7)创建测试类
- 在 src 目录下创建一个名为 com.test 的包,在包中创建 DemoTest 类,在该类中调用数据访问接口中的方法。
package com.test;
import com.mapper.UserMapper;
import com.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoTest {
public static void main(String[] args) {
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
UserMapper userMapper = (UserMapper)applicationContext.getBean("userMapper");
User user = userMapper.selectUserById(1);
System.out.println(user);
}
}
8)运行查看结果
1 张三 女
第6章 映射器元素详解
- 在映射文件中,
元素是映射文件的根元素,其他元素都是它的子元素。
6.1 <mappers>元素
-
元素用于指定MyBatis映射文件的位置,一般可以使用以下4种方法引入映射器文件,具体如下:
1)使用类路径引入:
<mappers>
<mapper resource="com/mybatis/mapper/UserMapper.xml"/>
</mappers>
2)使用本地文件路径引入:
<mappers>
<mapper url="file:///D:/com/mybatis/mapper/UserMapper.xml"/>
</mappers>
3)使用接口类引入:
<mappers>
<mapper class="com.mybatis.mapper.UserMapper"/>
</mappers>
4)使用包名引入(常用):
<mappers>
<package name="com.mybatis.mapper"/>
</mappers>
6.1.1 <select>元素
- 在 SQL 映射文件中 <select> 元素用于映射 SQL 的 select 语句,其示例代码如下:
<!--根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer" resultType="com.mybatis.po.MyUser">
select * from user where uid = #{uid}
</select>
在上述示例代码中,id 的值是唯一标识符,它接收一个 Integer 类型的参数,返回一个 MyUser 类型的对象,结果集自动映射到 MyUser 属性。
- <select> 元素除了有上述示例代码中的几个属性以外,还有一些常用的属性,如下所示:
属性名称 | 描 述 |
---|---|
id |
它和 Mapper 的命名空间组合起来使用, 是唯一标识符,供 MyBatis 调用 |
parameterType |
表示传入 SQL 语句的参数类型的全限定名或别名。 它是一个可选属性,MyBatis 能推断出具体传入语句的参数 |
resultType |
SQL 语句执行后返回的类型(全限定名或者别名)。 如果是集合类型,返回的是集合元素的类型,返回时可以使用 resultType 或 resultMap 之一 |
resultMap |
它是映射集的引用,与 <resultMap> 元素一起使用, 返回时可以使用 resultType 或 resultMap 之一 |
flushCache |
用于设置在调用 SQL 语句后是否要求 MyBatis 清空之前查询的本地缓存和二级缓存, 默认值为 false,如果设置为 true, 则任何时候只要 SQL 语句被调用都将清空本地缓存和二级缓存 |
useCache | 启动二级缓存的开关,默认值为 true,表示将査询结果存入二级缓存中 |
timeout |
用于设置超时参数,单位是秒(s),超时将抛出异常 |
fetchSize | 获取记录的总条数设定 |
statementType |
告诉 MyBatis 使用哪个 JDBC 的 Statement 工作, 取值为 STATEMENT(Statement)、 PREPARED(PreparedStatement)、 CALLABLE(CallableStatement) |
resultSetType |
这是针对 JDBC 的 ResultSet 接口而言, 其值可设置为 FORWARD_ONLY(只允许向前访问)、 SCROLL_SENSITIVE(双向滚动,但不及时更新)、SCROLLJNSENSITIVE(双向滚动,及时更新) |
6.1.1.1 使用 Map 接口传递多个参数
- 在实际开发中,查询 SQL 语句经常需要多个参数,例如多条件查询。在 MyBatis 中允许 Map 接口通过键值对传递多个参数。
1)在数据操作接口中实现查询陈姓男性用户信息功能的方法
public List<User> selectAllUser1(Map<String,Object> param);
2)SQL 映射文件代码
<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser1" resultType="com.pojo.User">
select * from user
where uname like concat('%',#{u_name},'%')
and usex = #{u_sex}
</select>
3)测试类中的代码
//查询多个用户
Map<String,Object> map = new HashMap<>();
map.put("u_name","陈");
map.put("u_sex","男");
List<User> list = userMapper.selectAllUser1(map);
for(User user : list) {
System.out.println(user);
}
4) 运行结果
2 陈恒 男
3 陈成 男
6.1.1.2 使用 Java Bean 传递多个参数
**1)创建 SeletUserParam **
- 在 com.pojo 包中创建一个 POJO 类 SeletUserParam,代码如下:
package com.pojo;
public class SelectUserParam {
private String u_name;
private String u_sex;
public String getU_name() {
return u_name;
}
public void setU_name(String u_name) {
this.u_name = u_name;
}
public String getU_sex() {
return u_sex;
}
public void setU_sex(String u_sex) {
this.u_sex = u_sex;
}
}
2)修改 Dao 接口中的 selectAllUser 方法
public List<User> selectAllUser(SelectUserParam param);
3)修改 SQL 映射文件 UserMapper.xml
- 将SQL 映射文件 UserMapper.xml 中的“查询陈姓男性用户信息”的代码修改为如下:
<select id="selectAllUser" resultType="com.pojo.User" parameterType="com.pojo.SelectUserParam">
select * from user
where uname like concat('%',#{u_name},'%')
and usex=#{u_sex}
</select>
4)测试类中的代码
SelectUserParam su = new SelectUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<User> list = userMapper.selectAllUser(su);
for (User myUser : list) {
System.out.println(myUser);
}
5)运行结果
2 陈恒 男
3 陈成 男
6.1.2 <insert>元素
-
元素用于映射插入语句,在执行完元素中定义的SQL语句后,会返回一个表示插入记录数的整数。 - 它的属性与 <select> 元素的属性大部分相同,在本节讲解它的几个特有属性。
- keyProperty:该属性的作用是将插入或更新操作时的返回值赋给 PO 类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。
- keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第 1 列时需要设置。如果是联合主键,可以将多个值用逗号隔开。
- useGeneratedKeys:该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取由数据库内部产生的主键,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false。
6.1.2.1 主键(自动递增)回填
- MySQL、SQL Server 等数据库的表格可以采用自动递增的字段作为主键,有时可能需要使用这个刚刚产生的主键,用于关联其他业务。
1)SQL 映射文件代码
- 首先为 SQL 映射文件 UserMapper.xml 中 id 为 addUser 的
元素添加 keyProperty 和 useGeneratedKeys 属性,具体代码如下:
<!--添加一个用户,成功后将主键值返回填给uid(po的属性)-->
<insert id="addUser" parameterType="com.po.MyUser" keyProperty="uid" useGeneratedKeys="true">
insert into user (uname,usex) values(#{uname},#{usex})
</insert>
2)测试类代码
// 添加一个用户
User addmu = new User();
addmu.setUname("陈恒");
addmu.setUsex("男");
int add = userMapper.addUser(addmu);
System.out.println("添加了" + add + "条记录");
System.out.println("添加记录的主键是" + addmu.getUid());
3)运行结果
添加了1条记录
添加记录的主键是4
6.1.2.2 自定义主键
- 如果在实际工程中使用的数据库不支持主键自动递增(例如 Oracle),或者取消了主键自动递增的规则,可以使用 MyBatis 的
元素来自定义生成主键。
<!-- 添加一个用户,#{uname}为 com.pojo.User 的属性值 -->
<insert id="addUser" parameterType="com.pojo.User">
<!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
<selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
select if(max(uid) is null,1,max(uid)+1) as newUid from user
</selectKey>
insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>
- 在执行上述示例代码时,
元素首先被执行,该元素通过自定义的语句设置数据表的主键,然后执行插入语句。 -
元素的 keyProperty 属性指定了新生主键值返回给 PO 类(com.po.MyUser)的哪个属性。
order 属性可以设置为 BEFORE 或 AFTER。
BEFORE 表示先执行元素然后执行插入语句。 AFTER 表示先执行插入语句再执行 元素。
6.1.3 <update>与<delete>元素
- <update> 和 <delete> 元素比较简单,它们的属性和 <insert> 元素、<select> 元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。
<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.pojo.User">
update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
delete from user where uid = #{uid}
</delete>
6.1.4 <sql> 元素
-
元素的作用在于可以定义 SQL 语句的一部分(代码片段),以方便后面的 SQL 语句引用它,例如反复使用的列名。 - 在 MyBatis 中只需使用 <sql> 元素编写一次便能在其他元素中引用它。
<sql id="comColumns">uid,uname,usex</sql>
<select id="selectUser" resultType="com.pojo.User">
select <include refid="comColumns"/> from user
</select>
6.1.5 <resultMap> 元素
- <resultMap> 元素包含了一些子元素,结构如下:
<resultMap id="" type="">
<constructor><!-- 类再实例化时用来注入结果到构造方法 -->
<idArg/><!-- ID参数,结果为ID -->
<arg/><!-- 注入到构造方法的一个普通结果 -->
</constructor>
<id/><!-- 用于表示哪个列是主键 -->
<result/><!-- 注入到字段或JavaBean属性的普通结果 -->
<association property=""/><!-- 用于一对一关联 -->
<collection property=""/><!-- 用于一对多、多对多关联 -->
<discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
<case value=""/><!-- 基于某些值的结果映射 -->
</discriminator>
</resultMap>
<resultMap> 元素的 type 属性表示需要的 POJO,id 属性是 resultMap 的唯一标识。
子元素 <constructor> 用于配置构造方法(当 POJO 未定义无参数的构造方法时使用)。
子元素 <id> 用于表示哪个列是主键。
子元素 <result> 用于表示POJO和数据表普通列的映射关系。
子元素 <association>、<collection> 和 <discriminator> 用在级联的情况下。
6.1.5.1 使用 Map 存储结果集
1)SQL 映射文件代码
<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
select * from user
</select>
2)UserMapper接口中添加方法
public List<Map<String,Object>> selectAllUserMap();
3)测试类代码
// 查询所有用户信息存到Map中
List<Map<String, Object>> lmp = userMapper.selectAllUserMap();
for (Map<String, Object> map : lmp) {
System.out.println(map);
}
4)运行结果
{uid=1, uname=张三, usex=女}
{uid=2, uname=陈恒, usex=男}
{uid=3, uname=陈成, usex=男}
{uid=4, uname=陈恒, usex=男}
6.1.5.2 使用POJO存储结果集
1)创建 POJO 类
- 在 com.pojo 包中创建 POJO 类 MapUser。MapUser 类的代码如下:
package com.pojo;
public class MapUser {
private Integer m_uid;
private String m_uname;
private String m_usex;
// 此处省略setter和getter方法
@Override
public String toString() {
return "User[uid=" + m_uid + ",uname=" + m_uname + ",usex=" + m_usex
+ "]";
}
}
2)配置 <resultMap> 元素
- 在 SQL 映射文件 UserMapper.xml 中配置 <resultMap> 元素,其属性 type 引用 POJO 类。具体配置如下:
<!--使用自定义结果集类型-->
<resultMap type="com.pojo.MapUser" id="myResult">
<!-- property 是 com.pojo.MapUser 类中的属性-->
<!-- column是查询结果的列名,可以来自不同的表-->
<id property="m_uid" column="uid"/>
<result property="m_uname" column="uname"/>
<result property="m_usex" column="usex"/>
</resultMap>
3)配置 <select> 元素
- 在 SQL 映射文件 UserMapper.xml 中配置 <select> 元素,其属性 resultMap 引用了 <resultMap> 元素的 id。具体配置如下:
<!-- 使用自定义结果集类型查询所有用户 -->
<select id="selectResultMap" resultMap="myResult">
select * from user
</select>
4)添加接口方法
public List<MapUser> selectResultMap();
5)测试类代码
// 使用resultMap映射结果集
List<MapUser> listResultMap = userMapper.selectResultMap();
for (MapUser myUser : listResultMap) {
System.out.println(myUser);
}
6)运行结果
User[uid=1,uname=张三,usex=女]
User[uid=2,uname=陈恒,usex=男]
User[uid=3,uname=陈成,usex=男]
User[uid=4,uname=陈恒,usex=男]
第7章 MyBatis关联查询(级联查询)
- 级联关系是一个数据库实体的概念,有 3 种级联关系,分别是一对一级联、一对多级联以及多对多级联。
- 如果表 A 中有一个外键引用了表 B 的主键,A 表就是子表,B 表就是父表。当查询表 A 的数据时,通过表 A 的外键将表 B 的相关记录返回,这就是级联查询。
7.1 一对一关联查询
- 在 MyBatis 中,通过 <resultMap> 元素的子元素 <association> 处理这种一对一级联关系。
- 在 <association> 元素中通常使用以下属性。
property:指定映射到实体类的对象属性。
column:指定表中对应的字段(即查询返回的列名)。
javaType:指定映射到实体对象属性的类型。
select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
7.1.1 实例
1)创建数据表
CREATE TABLE idcard (
id tinyint(2) NOT NULL AUTO_INCREMENT,
code varchar(18) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE person (
id tinyint(2) NOT NULL,
name varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
age int(11) DEFAULT NULL,
idcard_id tinyint(2) DEFAULT NULL,
PRIMARY KEY (id),
KEY idcard_id (idcard_id),
CONSTRAINT idcard_id FOREIGN KEY (idcard_id) REFERENCES idcard(id));
2)创建持久化类
- 在 myBatisDemo02 应用的 com.po 包中创建数据表对应的持久化类 Idcard 和 Person。
package com.po;
public class Idcard {
private Integer id;
private String code;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 为方便测试,重写了toString方法
*/
@Override
public String toString() {
return "Idcard [id=" + id + ",code=" + code + "]";
}
}
package com.po;
public class Person {
private Integer id;
private String name;
private Integer age;
// 个人身份证关联
private Idcard card;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Idcard getCard() {
return card;
}
public void setCard(Idcard card) {
this.card = card;
}
@Override
public String toString() {
return "Person[id=" + id + ",name=" + name + ",age=" + age + ",card="
+ card + "]";
}
}
3)创建映射文件
- 在 MyBatis 的核心配置文件 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>
<!--在使用MyBatis嵌套查询方式进行关联查询时,使用MyBatis的延迟加载可以在一定程度上提高查询效率-->
<settings>
<!--打开延迟加载的开关-->
<setting name= "lazyLoadingEnabled" value= "true"/>
<!--将积极加载改为按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
</configuration>
- 在 myBatisDemo02 应用的 com.mapper 中创建两张表对应的映射文件 IdCardMapper.xml 和 PersonMapper.xml。在 PersonMapper.xml 文件中以 3 种方式实现“根据 id 查询个人信息”的功能,详情请看代码备注。
- IdCardMapper.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="com.mapper.IdCardMapper">
<select id="selectCodeById" parameterType="Integer" resultType= "com.pojo.Idcard">
select * from idcard where id=#{id}
</select>
</mapper>
- PersonMapper.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="com.mapper.PersonMapper">
<!-- 一对一根据id查询个人信息:级联查询的第一种方法(嵌套查询,执行两个SQL语句)-->
<resultMap type="com.pojo.Person" id="cardAndPerson1">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一级联查询-->
<association property="card" column="idcard_id" javaType="com.pojo.Idcard"
select="com.mapper.IdCardMapper.selectCodeById"/>
</resultMap>
<select id="selectPersonById1" parameterType="Integer" resultMap=
"cardAndPerson1">
select * from person where id=#{id}
</select>
<!--对一根据id查询个人信息:级联查询的第二种方法(嵌套结果,执行一个SQL语句)-->
<resultMap type="com.pojo.Person" id="cardAndPerson2">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 一对一级联查询-->
<association property="card" javaType="com.pojo.Idcard">
<id property="id" column="idcard_id"/>
<result property="code" column="code"/>
</association>
</resultMap>
<select id="selectPersonById2" parameterType="Integer" resultMap= "cardAndPerson2">
select p.*,ic.code
from person p, idcard ic
where p.idcard_id=ic.id and p.id=#{id}
</select>
<!-- 一对一根据id查询个人信息:连接查询(使用POJO存储结果)-->
<select id="selectPersonById3" parameterType="Integer" resultType= "com.pojo.SelectPersonById">
select p.*,ic.code
from person p, idcard ic
where p.idcard_id = ic.id and p.id=#{id}
</select>
</mapper>
4)创建 POJO 类
- 在 myBatisDemo02 应用的 com.pojo 包中创建在第 3 步中使用的 POJO 类 SelectPersonById。
package com.pojo;
public class SelectPersonById {
private Integer id;
private String name;
private Integer age;
private String code;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "Person [id=" +id+",name=" +name+ ",age=" +age+ ",code=" +code+ "]";
}
}
5)创建数据操作接口
- 在 myBatisDemo02 应用的 com.mapper 包中创建第 3 步中映射文件对应的数据操作接口 IdCardMapper 和 PersonMapper。
package com.mapper;
import com.pojo.Idcard;
public interface IdCardMapper {
public Idcard selectCodeById(Integer i);
}
package com.mapper;
import com.pojo.Person;
import com.pojo.SelectPersonById;
public interface PersonMapper {
public Person selectPersonById1(Integer id);
public Person selectPersonById2(Integer id);
public SelectPersonById selectPersonById3(Integer id);
}
6)调用接口方法及测试
- 在 myBatisDemo02 应用的 com.controller 包中创建 OneToOneController 类,在该类中调用第 5 步的接口方法,同时创建测试类 TestOneToOne。
package com.controller;
import com.mapper.PersonMapper;
import com.pojo.Person;
import com.pojo.SelectPersonById;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller("oneToOneController")
public class OneToOneController {
@Autowired
private PersonMapper personMapper;
public void test(){
Person p1 = personMapper.selectPersonById1(1);
System.out.println(p1);
System.out.println("=============================");
Person p2 = personMapper.selectPersonById2(1);
System.out.println(p2);
System.out.println("=============================");
SelectPersonById p3 = personMapper.selectPersonById3(1);
System.out.println(p3);
}
}
package com.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestOneToOne {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
OneToOneController oto = (OneToOneController)applicationContext.getBean("oneToOneController");
oto.test();
}
}
7)运行结果
Person[id=1,name=张三,age=20,card=Idcard [id=1,code=1111]]
= ============================
Person[id=1,name=张三,age=20,card=Idcard [id=1,code=1111]]
= ============================
Person [id=1,name=张三,age=20,code=1111]
7.2 一对多关联查询(级联查询)
7.2.1 实例
1)创建数据表
- 本实例需要两张数据表,一张是用户表 user,一张是订单表 orders,这两张表具有一对多的级联关系。user 表在前面已创建,orders 表的创建代码如下:
CREATE TABLE orders (
id tinyint(2) NOT NULL AUTO_INCREMENT,
ordersn varchar(10) DEFAULT NULL,
user_id tinyint(2) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2)创建持久化类
- 在 myBatisDemo02 应用的 com.po 包中创建数据表 orders 对应的持久化类 Orders,user 表对应的持久化类 MyUser 在前面已创建,但需要为 MyUser 添加如下属性:
// 一对多级联查询,用户关联的订单
private List<Orders> ordersList;
- Orders 类的代码如下:
package com.po;
public class Orders {
private Integer id;
private String ordersn;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getOrdersn() {
return ordersn;
}
public void setOrdersn(String ordersn) {
this.ordersn = ordersn;
}
@Override
public String toString() {
return "Orders[id=" + id + ",ordersn=" + ordersn + "]";
}
}
3)创建映射文件
-
在 myBatisDemo02 应用的 com.mapper 中创建两张表对应的映射文件 UserMapper.xml 和 OrdersMapper.xml。映射文件 UserMapper.xml 在前面已创建,但需要添加以下配置才能实现一对多级联查询(根据 uid 查询用户及其关联的订单信息):
-
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">
<mapper namespace="com.mapper.UserMapper">
<!-- 一对多 根据uid查询用户及其关联的订单信息:级联查询的第一种方法(嵌套查询) -->
<resultMap type="com.po.MyUser" id="userAndOrders1">
<id property="uid" column="uid" />
<result property="uname" column="uname" />
<result property="usex" column="usex" />
<!-- 一对多级联查询,ofType表示集合中的元素类型,将uid传递给selectOrdersByld -->
<collection property="ordersList" ofType="com.po.Orders"
column="uid" select="com.mapper.OrdersMapper.selectOrdersById" />
</resultMap>
<select id="selectOrdersById1" parameterType="Integer"
resultMap="userAndOrders1">
select * from user where uid = #{id}
</select>
<!--对多根据uid查询用户及其关联的订单信息:级联查询的第二种方法(嵌套结果) -->
<resultMap type="com.po.MyUser" id="userAndOrders2">
<id property="uid" column="uid" />
<result property="uname" column="uname" />
<result property="usex" column="usex" />
<!-- 对多级联查询,ofType表示集合中的元素类型 -->
<collection property="ordersList" ofType="com.po.Orders">
<id property="id" column="id" />
<result property="ordersn" column="ordersn" />
</collection>
</resultMap>
<select id="selectOrdersById2" parameterType="Integer"
resultMap="userAndOrders2">
select u.*,o.id, o.ordersn from user u, orders o where u.uid
= o.user_id and
u.uid=#{id}
</select>
<!-- 一对多 根据uid查询用户及其关联的订单信息:连接查询(使用POJO存储结果) -->
<select id="selectOrdersById3" parameterType="Integer"
resultType="com.pojo.SelectUserOrdersById">
select u.*, o.id, o.ordersn from user u, orders o where
u.uid = o.user_id
and u.uid=#{id}
</select>
</mapper>
- OrdersMapper.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="com.mapper.OrdersMapper">
<!-- 根据用户uid查询订单信息 -->
<select id="selectOrdersById" resultType="com.po.MyUser"
parameterType="Integer">
select * from orders where user_id=#{id}
</select>
</mapper>
4)创建 POJO 类
- 在 myBatisDemo02 应用的 com.pojo 包中创建在第 3 步中使用的 POJO 类 SelectUserOrdersById。
package com.pojo;
public class SelectUserOrdersById {
private Integer uid;
private String uname;
private String usex;
private Integer id;
private String ordersn;
// 省略setter和getter方法
@Override
public String toString() { // 为了方便查看结果,重写了toString方法
return "User[uid=" + uid + ",uname=" + uname + ",usex=" + usex
+ ",oid=" + id + ",ordersn=" + ordersn + "]";
}
}
5)创建数据操作接口
- 在 myBatisDemo02 应用的 com.mapper 包中创建第 3 步中映射文件对应的数据操作接口 OrdersMapper 和 UserMapper。
package com.mapper;
import com.po.Orders;
import java.util.List;
public interface OrdersMapper {
public List<Orders> selectOrdersById(Integer uid);
}
package com.mapper;
import com.po.MyUser;
import com.pojo.SelectUserOrdersById;
import java.util.List;
public interface UserMapper {
public MyUser selectOrdersById1(Integer uid);
public MyUser selectOrdersById2(Integer uid);
public List<SelectUserOrdersById> selectOrdersById3(Integer uid);
}
6)调用接口方法及测试
- 在 myBatisDemo02 应用的 com.controller 包中创建 OneToMoreController 类,在该类中调用第 5 步的接口方法,同时创建测试类 TestOneToMore。
package com.controller;
import com.mapper.UserMapper;
import com.po.MyUser;
import com.pojo.SelectUserOrdersById;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import java.util.List;
@Controller("oneToMoreController")
public class OneToMoreController {
@Autowired
private UserMapper userMapper;
public void test(){
//查询一个用户及订单信息
MyUser auser1 = userMapper.selectOrdersById1(1);
System.out.println(auser1);
System.out.println("=============================");
MyUser auser2 = userMapper.selectOrdersById2(1);
System.out.println(auser2);
System.out.println("=============================");
List<SelectUserOrdersById> auser3 = userMapper.selectOrdersById3(1);
System.out.println(auser3);
System.out.println("=============================");
}
}
package com.controller;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestOneToMore {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
OneToMoreController otm = (OneToMoreController)applicationContext.getBean("oneToMoreController");
otm.test();
}
}
6)配置 applicationContext.xml
<!--配置userMapper对象-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!--配置ordersMapper对象-->
<bean id="ordersMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.mapper.OrdersMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
7)运行结果
User[uid=1,uname=张三,usex=女]
= ============================
User[uid=1,uname=张三,usex=女]
= ============================
[User[uid=1,uname=张三,usex=女,oid=1,ordersn=order1]]
= ============================
7.3 多对多关联查询(级联查询)*
7.3.1 实例
1)创建数据表
- 订单表在前面已创建,这里需要创建商品表 product 和订单记录表 orders_detail,创建代码如下:
CREATE TABLE product(
id tinyint(2) NOT NULL,
name varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
price double DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE orders_detail(
id tinyint(2) NOT NULL AUTO_INCREMENT,
orders_id tinyint(2) DEFAULT NULL,
product_id tinyint(2) DEFAULT NULL,
PRIMARY KEY (id),
KEY orders_id (orders_id),
KEY product_id (product_id),
CONSTRAINT orders_id FOREIGN KEY (orders_id) REFERENCES orders (id),
CONSTRAINT product_id FOREIGN KEY (product_id) REFERENCES product (id)
);
第8章 动态SQL
- 创建 myBatisDemo03 应用,并将 MyBatis-Spring 应用的所有 JAR 包和 src 中所有 Java 程序与 XML 文件都复制到 myBatisDemo03 的相应位置。
8.1 if标签:条件判断
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用 if 元素根据条件动态查询用户信息-->
<select id="selectUserByIf" resultType="com.pojo.User" parameterType="com.pojo.User">
select * from user where 1=1
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if >
<if test="usex !=null and usex !=''">
and usex=#{usex}
</if >
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByIf(User user);
3)调用数据操作接口方法
// 使用 if 元素查询用户信息
User ifmu=new User();
ifmu.setUname ("张");
ifmu.setUsex ("女");
List<User> listByif=userMapper.selectUserByIf(ifmu);
System.out.println ("if元素================");
for (User user:listByif) {
System.out.println(user);
}
4)运行结果
if元素================
1 张三 女
8.2 choose、when、otherwise标签
- MyBatis 提供了 <choose> 元素,它有点像 Java 中的 switch 语句。
1)添加 SQL 映射语句
<!--使用choose、when、otherwise元素根据条件动态查询用户信息-->
<select id="selectUserByChoose" resultType="com.pojo.User" parameterType= "com.pojo.User">
select * from user where 1=1
<choose>
<when test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</when>
<when test="usex!=null and usex!=''">
and usex=#{usex}
</when>
<otherwise>
and uid > 1
</otherwise>
</choose>
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByChoose(User user);
3)调用数据操作接口方法
// 使用 choose 元素查询用户信息
User choosemu=new User();
choosemu.setUname("");
choosemu.setUsex("");
List<User> listByChoose = userMapper.selectUserByChoose(choosemu);
System.out.println ("choose 元素================");
for (User user:listByChoose) {
System.out.println(user);
}
4)运行结果
choose 元素================
2 陈恒 男
3 陈成 男
4 陈恒 男
8.3 trim、where、set标签
8.3.1 trim标签
- <trim> 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix。
- 可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides。正因为 <trim> 元素有这样的功能,所以也可以非常简单地利用 <trim> 来代替 <where> 元素的功能。
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用trim元素根据条件动态查询用户信息-->
<select id="selectUserByTrim" resultType="com.pojo.User" parameterType="com.pojo.User">
select * from user
<trim prefix="where" prefixOverrides = "and | or">
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex!=null and usex!=''">
and usex=#{usex}
</if>
</trim>
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByTrim(User user);
3)调用数据操作接口方法
// 使用trim元素查询用户信息
User trimmu=new User();
trimmu.setUname ("陈");
trimmu.setUsex("男");
List<User> listByTrim=userMapper.selectUserByTrim(trimmu);
System.out.println ("trim 元素=========================");
for (User user:listByTrim) {
System.out.println(user);
}
4)运行结果
trim 元素=========================
2 陈恒 男
3 陈成 男
4 陈恒 男
8.3.2 where标签
- <where> 元素的作用是会在写入 <where> 元素的地方输出一个 where 语句,另外一个好处是不需要考虑 <where> 元素里面的条件输出是什么样子的,MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。
- 当然如果是以 or 开头的,MyBatis 也会把它忽略;此外,在
元素中不需要考虑空格的问题,MyBatis 将智能加上。
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用where元素根据条件动态查询用户信息-->
<select id="selectUserByWhere" resultType="com.pojo.User" parameterType="com.pojo.User">
select * from user
<where>
<if test="uname!=null and uname!=''">
and uname like concat('%',#{uname},'%')
</if>
<if test="usex!=null and usex!=''">
and usex=#{usex}
</if >
</where>
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByWhere(User user);
3)调用数据操作接口方法
// 使用where元素查询用户信息
User wheremu=new User();
wheremu.setUname ("张");
wheremu.setUsex("女");
List<User> listByWhere=userMapper.selectUserByWhere(wheremu);
System.out.println ("where 元素=========================");
for (User user:listByWhere) {
System.out.println(user);
}
4)运行结果
where 元素=========================
1 张三 女
8.3.3 set标签
- 在动态 update 语句中可以使用 <set> 元素动态更新列。
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用set元素动态修改一个用户-->
<update id="updateUserBySet" parameterType="com.pojo.User">
update user
<set>
<if test="uname!=null">uname=#{uname}</if>
<if test="usex!=null">usex=#{usex}</if>
</set>
where uid=#{uid}
</update>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public int updateUserBySet(User user);
3)调用数据操作接口方法
// 使用set元素查询用户信息
User setmu=new User();
setmu.setUid (1);
setmu.setUname("张九");
int setup=userMapper.updateUserBySet(setmu);
System.out.println ("set 元素修改了"+setup+"条记录");
System.out.println ("=========================");
4)运行结果
set 元素修改了1条记录
8.4 foreach标签
-
<foreach> 元素主要用在构建 in 条件中,它可以在 SQL 语句中迭代一个集合。
-
<foreach> 元素的属性主要有 item、index、collection、open、separator、close。
item 表示集合中每一个元素进行迭代时的别名。 index 指定一个名字,用于表示在迭代过程中每次迭代到的位置。 open 表示该语句以什么开始。 separator 表示在每次进行迭代之间以什么符号作为分隔符。 close 表示以什么结束。
- 在使用 <foreach> 元素时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
- 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
- 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
- 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用foreach元素查询用户信息-->
<select id="selectUserByForeach" resultType="com.pojo.User" parameterType=
"List">
select * from user where uid in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByForeach(List<Integer> listId);
3)调用数据操作接口方法
//使用foreach元素查询用户信息
List<Integer> listId=new ArrayList<Integer>();
listId.add(1);
listId.add(3);
List<User> listByForeach = userMapper.selectUserByForeach(listId);
System.out.println ("foreach元素================");
for(User user : listByForeach) {
System.out.println(user);
}
4)运行结果
foreach元素================
1 张九 女
3 陈成 男
8.5 bind标签
- 在进行模糊查询时,如果使用“${}”拼接字符串,则无法防止 SQL 注入问题。如果使用字符串拼接函数或连接符号,但不同数据库的拼接函数或连接符号不同。
例如 MySQL 的 concat 函数、Oracle 的连接符号“ ”,这样 SQL 映射文件就需要根据不同的数据库提供不同的实现,显然比较麻烦,且不利于代码的移植。幸运的是,MyBatis 提供了 <bind> 元素来解决这一问题。
1)添加 SQL 映射语句
- 在 UserMapper.xml 文件中添加如下 SQL 映射语句:
<!--使用bind元素进行模糊查询-->
<select id="selectUserByBind" resultType="com.pojo.User" parameterType= "com.pojo.User">
<!-- bind 中的 uname 是 com.po.MyUser 的属性名-->
<bind name="paran_uname" value="'%' + uname + '%'"/>
select * from user where uname like #{paran_uname}
</select>
2)添加数据操作接口方法
- 在 com.mapper 包的 UserMapper 接口中添加如下数据操作接口方法:
public List<User> selectUserByBind(User user);
3)调用数据操作接口方法
// 使用bind元素查询用户信息
User bindmu=new User();
bindmu.setUname ("张");
List<User> listByBind=userMapper.selectUserByBind(bindmu);
System.out.println ("bind 元素=========================");
for (User user:listByBind) {
System.out.println(user);
}
4)运行结果
bind 元素=========================
1 张九 女
Comments