Mybatis教程
1 概述
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.5。
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis是一款典型的ORM映射工具。既Java是基于OO面向对象模型,而数据库时基于ER实体关系模型,这两种模型是不同的。Mybatis相当于这两种模型之间建立起映射关系。使得Java开发人员在访问数据库库时,不用在大脑中转换为ER模型,而保持OO模型方式访问和使用数据库中的数据。
本文以最新的Mybatis 3.5.5版本进行讲解。 同时讲解了与Spring 5.x和Spring Boot 2.x的整合。
2 直接使用JDBC的缺点
3 Mybatis基本框架
4 以例子说明
4.1 数据库表结构
这里使用MySQL数据库(版本为8.0.18)。数据库表的存储引擎使用InnoDB。数据库上已经创建了表,表结构如下:
Customer客户信息表
字段 | 字段名 | 类型 | 键 | 备注 |
---|---|---|---|---|
customer_ID | 客户ID | varchar(32),Not NULL | PK | 内部唯一标识一个客户 |
login_account | 登录账户:用户名 | varchar(20),Not NULL | Unique Index | 一个客户可以拥有多类登录方式(手机/邮箱/用户名等)。同类登录方式不重复。 |
login_email | 登录账户:邮箱 | varchar(48),NULL | Unique Index | 一个客户可以拥有多类登录方式(手机/邮箱/用户名等)。同类登录方式不重复。 |
customer_Name | 客户名字 | varchar(64),Not NULL | ||
customer_level | 客户级别 | int, Not NULL | 10:一级(普通), 20:二级, 30:三级… | |
status | 状态 | char(1),Not NULL | E=Enable有效,D=Disable失效 | |
create_datetime | 客户创建日期时间 | datetime,NOT NULL default CURRENT_TIMESTAMP |
Customer表中已经insert进了5条数据。
Order订单信息表
字段 | 字段名 | 类型 | 键 | 备注 |
---|---|---|---|---|
order_ID | 订单ID | varchar(64),Not NULL | PK | |
customer_ID | 订单所属的客户ID | varchar(32),Not NULL | FK | 外键关联Customer表 |
purchase_count | 购买商品的数量 | varchar(32),Not NULL | ||
order_address | 订单地址 | varchar(512),Not NULL | ||
actual_amount | 订单实际支付总金额 | int,Not NULL | 单位为分。 | |
order_status | 订单状态 | char(4),Not NULL | INIT=初始(未支付),PYED=已支付,DELY=运输中,CANL=已取消,FINS=结束。 | |
order_create_datetime | 订单创建日期和时间 | datetime,NOT NULL default CURRENT_TIMESTAMP |
4.2 新建Java工程
在Eclipse创建一个“Maven Project”。初始的工程目录结构如下:
在pom.xml
文件中增加以下内容:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zyp</groupId>
<artifactId>MyDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
</dependencies>
</project>
注意
:如果不是maven工程,需要手工将mybatis的jar放入classpath中。
4.3 Java代码
4.3.1 创建entity代码
因为数据库时基于ER实体关系模型。在Java中访问数据库,需要在Java中有对象存放数据库Table的内容。通常每张Table会建立一个entity的Java类。如果联合查询的结果有必要,也可以单独建立一个联合查询的entity类。
在工程中,创建com.zyp.mydemo.entity.Customer类,代码如下:
package com.zyp.mydemo.entity;
import java.time.LocalDateTime;
public class Customer {
private String customer_ID;
private String login_account;
private String login_email;
private String customer_Name;
private int customer_level;
private String status;
private LocalDateTime create_datetime;
... 所有属性的getter()和setter()方法。 (通过Eclipse自动生成) ....
... toString()方法。 (通过Eclipse自动生成) ....
}
创建com.zyp.mydemo.entity.Order类,代码如下:
package com.zyp.mydemo.entity;
import java.time.LocalDateTime;
public class Order {
private String order_ID;
private String customer_ID;
private String purchase_count;
private String order_address;
private int actual_amount;
private String order_status;
private LocalDateTime order_create_datetime;
... 所有属性的getter()和setter()方法。 (通过Eclipse自动生成) ....
... toString()方法。 (通过Eclipse自动生成) ....
}
4.3.2 创建Dao层
上面定义的entity相当于是表结构在Java中的映射(没有动作)。
而具体的访问数据库动作也需要在Java中定义,即作为访问数据库动作的Java类(多个)统称为Dao层。Dao层叫数据访问层,全称为data access object,属于一种比较底层,比较基础的操作,具体到dao对于某个表、某个实体的增删改查。
有了Dao层,在Java的业务逻辑处理过程只需要访问Dao层即可,不用在逻辑处理中写SQL语句。
**注意
:因Mybatis通过mapper方法实现了Dao层,所以很多地方把这里的Dao层称为mapper层(包括官方文档)。这里为了充分兼容两者,将package名为dao,但每个具体的Java类名以Mapper结尾。
创建接口com.zyp.mydemo.dao.CustomerMapper,代码如下(只是接口,没有实现类):
package com.zyp.mydemo.dao;
import java.util.List;
import com.zyp.mydemo.entity.Customer;
public interface CustomerMapper {
public Customer findOneByID (String c_id);
public List<Customer> findAll ();
public void updateOne(Customer customer);
}
创建接口com.zyp.mydemo.dao.OrderMapper,代码如下(只是接口,没有实现类):
package com.zyp.mydemo.dao;
import java.util.List;
import com.zyp.mydemo.entity.Order;
public interface OrderMapper {
public Order findOneByID (String o_id);
public List<Order> findAll ();
public void updateOne(Order customer);
}
4.4 XML配置
4.4.1 全局mybatis配置文件(mybatis-config.xml)
在src/main/resources目录下,增加一个mybatis-config.xml文件(这个文件名不是固定的),该文件是Myabtis的全局配置文件。内容如下:
<?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>
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
</properties>
<!-- 定义类型的别名(省略的前缀,类似于Java源代码中import的作用)。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。 这样在Mapper.xml中就不用写类的全名了。 -->
<typeAliases>
<package name="com.zyp.mydemo.entity" />
</typeAliases>
<!-- 环境,可以配置多个名字随意(比如开发dev,测试test等待)。 default:指定默认使用下面哪个环境 -->
<environments default="dev">
<environment id="dev">
<!-- 使用哪种事务管理器,这里指定为JDBC -->
<transactionManager type="JDBC" />
<!-- 数据源,连接池类型的数据源。 type有三种内建类型"[UNPOOLED|POOLED|JNDI],也可以选择第三方连接池 -->
<dataSource type="POOLED">
<!-- 这里将各项的值直接放入value中。 -->
<property name="driver" value="${driver}" />
<property name="url" value="jdbc:mysql://192.168.43.201:3306/db_seckill" />
<property name="username" value="root" />
<property name="password" value="Pwd_1234" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 指示mapper的位置。因为将*.xml文件与dao层放在了一起,且Dao层的接口名与*.xml文件名一致所以可用packge方式-->
<!-- 可以有多重方式查找。具体参见官方文档。 -->
<package name="com.zyp.mydemo.dao" />
</mappers>
</configuration>
注意
:通过上面package(或者class)方式指定mappers,需要确保:①在package路径下存在*.xml文件; ②而且xml的文件名需要与Dao接口名一致。
对于第①点,要么人工将resource/mappers目录下的*.xml文件移动至pacakge对于的目录下。 或者要么继续保留resource/mappers目录下,在maven中配置build,使得maven 在build时自动帮我们拷贝过去。
4.4.2 Mapper映射XML文件
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。下面创建两个Table的映射文件。
为了后上面的配置对象,我们将每个*Mapper.xml放在Dao层的同路径下。而且xml的文件名和Dao的接口名一致
在package=com.zyp.mydemo.dao下,增加一个CustomerMapper.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应等于对于Dao的Java接口全名。这样就不用人工写Dao接口的实现类。Mybatis会通过动态代理自动生成实现类 -->
<mapper namespace="com.zyp.mydemo.dao.CustomerMapper">
<select id="findOneByID" resultType="Customer">
select customer_ID, login_account, login_email, customer_Name,
customer_level, status, create_datetime
from Customer where customer_ID = #{c_id}
</select>
<select id="findAll" resultType="Customer">
select customer_ID, login_account, login_email, customer_Name,
customer_level, status, create_datetime
from Customer
</select>
<update id="updateOne" parameterType="Customer">
UPDATE Customer
<set>
<if test="login_account != null">login_account = #{login_account},</if>
<if test="login_email != null">login_email = #{login_email},</if>
<if test="customer_Name != null">customer_Name = #{customer_Name},</if>
<if test="customer_level != null">customer_level = #{customer_level},</if>
<if test="status != null">status = #{status},</if>
<if test="create_datetime != null">create_datetime = #{create_datetime}</if>
</set>
WHERE
(customer_ID = #{customer_ID});
</update>
</mapper>
在package=com.zyp.mydemo.dao下,,增加一个OrderMapper.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应等于对于Dao的Java接口全名。这样就不用人工写Dao接口的实现类。Mybatis会通过动态代理自动生成实现类 -->
<mapper namespace="com.zyp.mydemo.dao.OrderMapper">
<select id="findOneByID" resultType="Order">
select order_ID, customer_ID, purchase_count, order_address, actual_amount,
order_status, order_create_datetime
from Order where order_ID = #{o_id}
</select>
<select id="findAll" resultType="Order">
select order_ID, customer_ID, purchase_count, order_address, actual_amount,
order_status, order_create_datetime
from Customer
</select>
<update id="updateOne" parameterType="Order">
UPDATE Order
<set>
<if test="customer_ID != null">customer_ID = #{customer_ID},</if>
<if test="purchase_count != null">purchase_count = #{purchase_count},</if>
<if test="order_address != null">order_address = #{order_address},</if>
<if test="actual_amount != null">actual_amount = #{actual_amount}</if>
<if test="order_status != null">order_status = #{order_status}</if>
<if test="order_create_datetime != null">order_create_datetime = #{order_create_datetime},</if>
</set>
WHERE
(order_ID = #{order_ID});
</update>
</mapper>
4.5 验证
4.5.1 测试代码
创建测试类com.zyp.mydemo.Test,代码如下:
package com.zyp.mydemo;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.zyp.mydemo.dao.CustomerMapper;
import com.zyp.mydemo.entity.Customer;
public class Test {
private static SqlSessionFactory sqlSessionFactory;
public static void main(String[] args) {
try {
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 构建sqlSessionFactory, 全局创建一次即可。一旦被创建就应该在应用的运行期间一直存在
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 调用测试方法
testSelectCustomer();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testSelectCustomer() {
// openSession的入参为autoCommit=false
try (SqlSession sqlSession = sqlSessionFactory.openSession(false)) { // try-with_resource语法,不用关闭对象,会自动关闭。
// CRUD操作:
// 第一个参数:指定statement。 规则:Mapper.xml中的namespace命名空间+“.”+statementId
// 第二个参数:指定传入sql需要的入参:这里是客户c_id
Customer customer1 = sqlSession.selectOne("com.zyp.mydemo.dao.CustomerMapper.findOneByID", "CID2000000000001");
System.out.println("结果1:\n" + customer1);
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class); // 这么用,要求Mapper.xml中namespace的值等于类全名
Customer customer2 = customerMapper.findOneByID("CID2000000000002");
System.out.println("结果2:\n" + customer2);
customer2.setCustomer_Name("NEW_NAME");
customerMapper.updateOne(customer2);
sqlSession.commit(); // 更新后提交
Customer customer2_new = customerMapper.findOneByID("CID2000000000002");
System.out.println("结果2(更新后):\n" + customer2_new);
}
}
}
测试结果
结果1:
Customer [customer_ID=CID2000000000001, login_account=LOGIN2000000000001, login_email=13300000001@163.com, customer_Name=NAME2000000000001, customer_level=10, status=E, create_datetime=2020-06-07T00:32:26]
结果2:
Customer [customer_ID=CID2000000000002, login_account=LOGIN2000000000002, login_email=13300000002@163.com, customer_Name=NAME2000000000002, customer_level=10, status=E, create_datetime=2020-06-07T00:32:26]
结果2(更新后):
Customer [customer_ID=CID2000000000002, login_account=LOGIN2000000000002, login_email=13300000002@163.com, customer_Name=NEW_NAME, customer_level=10, status=E, create_datetime=2020-06-06T11:32:26]
4.5.2 工程结构
5 Mybatis使用步骤
- 配置mybatis-config.xml 全局的配置文件
- 在Java中分别定义entity类
- 在Java定义Dao层接口,配置每个*Mapper.xml文件。 (可以把这两者放在同一个目录下)
- 创建全局SqlSessionFactory对象(入参为mybatis-config.xml 全局的配置文件)。 此Factory只需要创建一次。
- 每次使用的时候,通过SqlSessionFactory创建SqlSession对象(从Pool连接池中获得session)。
- 通过SqlSession获得Dao对象,使用Dao层操作数据库 CRUD。 (也可以直接使用SqlSession操作数据库CRUD)。
- 调用sqlsession.commit()提交事务,或者rollback。
- 使用完session后,调用sqlsession.close()关闭会话。(使用try-with-resource语法会自动关闭,不用手动关闭)。
注意
:上面的步骤中并没有写Dao接口的实现类。因为我们通常让Mybatis就会通过动态代理度自动生成实现代码(这也是Mybatis官方推荐的做法),不用人工写实现代码。但满足以下几个条件:
- 在*Mapper.xml中namespace设置为正确的Dao层接口名(类全名)。
- 在*Mapper.xml中语句id必须与Dao层接口中的方法名一致。
- 在*Mapper.xml的parameterType参数类型,必须与Dao层接口方法的参数类型一致。
- 在*Mapper.xml的resultType结果类型,必须与Dao层接口方法的返回类型一致。
6、进一步研究
6.1 事务管理transactionManager
在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):
- JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源datasource获得的连接来管理事务作用域。
- 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) { // 从Mybatis 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 对事务的处理。
6.2 dataSource连接池Pool设置
mybatis-config.xml全局配置中,dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
- 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型, 也就是 type="[UNPOOLED|POOLED|JNDI]"
6.2.1 type="UNPOOLED"
这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
这个type指定的datasource类型实际上等于“org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory”
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
属性给数据库驱动。
6.2.2 type="POOLED"
这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
这个type指定的datasource类型实际上等于“org.apache.ibatis.datasource.pooled.PooledDataSourceFactory”
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
– 在任意时间可存在的活动(正在使用)连接数量,默认值:10poolMaximumIdleConnections
– 任意时间可能存在的空闲连接数。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 时适用)。
6.2.3 type="JNDI"
这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。
这个type指定的datasource类型实际上等于“org.apache.ibatis.datasource.jndi.JndiDataSourceFactory”
这种数据源配置只需要两个属性:
initial_context
– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。data_source
– 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如:
env.encoding=UTF8
6.2.4 type=其他第三方
可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory
来使用第三方数据源实现。该官方接口定义如下:
package org.apache.ibatis.datasource;
import java.util.Properties;
import javax.sql.DataSource;
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
已经实现了DataSourceFactory
接口,因此UnpooledDataSourceFactory
可被用作父类来构建新的数据源适配器。比如,在加入C3P0的jar包后,增加下面一个Java类以使用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(); // 这时C3P0的对象
}
}
注:因为C3P0官方jar并没有继承上面的接口,是无法在Mybatis中直接使用C3P0的。 因此我们通过构建下面的java类来使用C3P0(相当于通过该类实现了Mybatis与C3P0的桥接)。这样相当于创建了一个自定义名为C3P0DataSourceFactory
的DataSource(内部包装了C3P0)。
在上面的Java代码写好后,还需要在mybatis-config.xml全局配置中,按以下样例进行配置(下面仅部分属性,还有其他属性可以参考C3P0官网):
<dataSource type="com.zyp.datasource.C3P0DataSourceFactory">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.43.201:3306/db_seckill"/>
<property name="username" value="root"/>
<property name="password" value="Pwd_1234"/>
</dataSource>
6.3 读取外部配置文件
在前面的例子数据库的连接信息是直接写在mybatis-config.xml全局配置中的,实际情况经常数据连接信息单独放在一份文件(位置可以任意)。这样的好处时:因为mybatis-config.xml全局配置通常会和jar一起打包,当数据库连接信息有变化时需要修改jar文件比较麻烦且危险。 若把数据库连接信息单独出来(放在jar包之外),则数据库连接信息有变化时只需要修改这个单独文件,不需要修改mybatis-config.xml文件,就不用修改jar文件。
下面演示单独配置文件的做法。
与mybatis-config.xml同目录下(既src/main/resources目录下),新创建文本文件db.properties(这里为简单,没有把此文件放在其他位置)。内容如下:
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.url=jdbc:mysql://192.168.43.221:3306/db_seckill
datasource.username=root
datasource.password=Pwd_1234
然后将mybatis-config.xml修改为(改动了两处:①properties标签的属性值; ②dataSource标签下的属性值):
<?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">
</properties>
<!-- 定义类型的别名。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。 这样在Mapper.xml中就不用写类的全名了。 -->
<typeAliases>
<package name="com.zyp.mydemo.entity" />
</typeAliases>
<!-- 环境,可以配置多个名字随意(比如开发dev,测试test等待)。 default:指定默认采用哪个环境 -->
<environments default="dev">
<environment id="dev">
<!-- 使用哪种事务管理器,这里指定为JDBC -->
<transactionManager type="JDBC" />
<!-- 数据源,连接池类型的数据源。 type有三种内建类型"[UNPOOLED|POOLED|JNDI],也可以选择第三方连接池 -->
<dataSource type="POOLED">
<!-- 这里将各项的值直接放入value中。在实际项目,这里只写变量,而将具体值放入单独一个文件中。当数据库连接信息变动时就不用改本文件 -->
<property name="driver" value="${datasource.driver}" />
<property name="url" value="${datasource.url}" />
<property name="username" value="${datasource.username}" />
<property name="password" value="${datasource.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 指示mapper的位置。因为将*.xml文件与dao层放在了一起,且Dao层的接口名与*.xml文件名一致-->
<!-- 可以有多重方式查找。具体参见官方文档。 -->
<package name="com.zyp.mydemo.dao" />
</mappers>
</configuration>
以上修改后。再次测试,可以发现运行正常。
6.4 Mapper XML文件说明
6.4.1 CRUD标签
select
select – 书写查询sql语句
select中的几个属性说明:
id属性:当前名称空间下的statement的唯一标识。必须。要求id和mapper接口中的方法的名字一致。
resultType:将结果集映射为java的对象类型。必须(和 resultMap 二选一)
parameterType:传入参数类型。可以省略
insert
insert 的几个属性说明:
id:唯一标识,随便写,在同一个命名空间下保持唯一,使用动态代理之后要求和方法名保持一致
parameterType:参数的类型,使用动态代理之后和方法的参数类型一致
useGeneratedKeys:开启主键回写
keyColumn:指定数据库的主键
keyProperty:主键对应的pojo属性名
标签内部:具体的sql语句。
update
id属性:当前名称空间下的statement的唯一标识(必须属性);
parameterType:传入的参数类型,可以省略。
标签内部:具体的sql语句。
delete
delete 的几个属性说明:
id属性:当前名称空间下的statement的唯一标识(必须属性);
parameterType:传入的参数类型,可以省略。
标签内部:具体的sql语句。
6.4.2 #{}和${}的区别
举个例子:
Mapper.xml文件和Dao层节选,如下:
<select id="queryUserByTableName" resultType="com.zpc.mybatis.pojo.User">
select * from #{tableName}
</select>
---- 分隔符 ----
/**
* 根据表名查询用户信息(直接使用注解指定传入参数名称)
*
* @param tableName
* @return
*/
public List<User> queryUserByTableName(String tableName);
测试结果:
报语法错误:相当于执行了这样一条sql:
select * from “tb_user”;
显然表名多了引号。
将Mapper.xml改为一下后,结果正确。
<select id="queryUserByTableName" resultType="com.zpc.mybatis.pojo.User">
select * from ${tableName}
</select>
结论:
#{} 只是替换?,相当于PreparedStatement使用占位符去替换参数,可以防止sql注入(例如:即使参数里有敏感字符如 or ‘1=1’,也会作为整体作为一个字符串参数来处理,而不是视为SQL条件语句) 。
$ {} 是进行字符串拼接(类似于Bash Shell中的变量),相当于sql语句中的Statement,使用字符串去拼接sql;$可以是sql中的任一部分传入到Statement中,不能防止sql注入。
特别注意:#{} 只是表示占位,与参数的名字无关。 如果只有一个参数,会自动对应。
如下,#{}多个参数时:
<select id="login" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user where user_name = #{userName} and password = #{password}
</select>
---- 分隔符 ----
/**
* 登录(直接使用注解指定传入参数名称)
*
* @param userName
* @param password
* @return
*/
public User login( String userName, String password);
报错:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'userName' not found. Available parameters are [0, 1, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'userName' not found. Available parameters are [0, 1, param1, param2]
解决方案一:修改XML为顺序的占位序号
<select id="login" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user where user_name = #{0} and password = #{1}
</select>
解决方案二:修改XML为顺序的占位序号,另一种表示方式
<select id="login" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user where user_name = #{param1} and password = #{param2}
</select>
解决方案三:最可靠的方法: 方法的参数列表上加上一个注释@Param(“xxxx”) 显式指定参数的名字,然后通过${“xxxx”}或#{“xxxx”}
<select id="login" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user where user_name = #{userName} and password = #{password}
</select>
---- 分隔符 ----
/**
* 登录(直接使用注解指定传入参数名称)
*
* @param userName
* @param password
* @return
*/
public User login(@Param("userName") String userName, @Param("password") String password);
注意:${}不会自动加引号。再需要引号的场景,需要手工加引号。例如:
/**
* #号 测试
* @param username1
* @return
*/
User queryUserListByName1(@Param("username1") String username1);
/**
* $号 测试
* @param username2
* @return
*/
User queryUserListByName2(@Param("username2") String username2);
<select id="queryUserListByName1" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user WHERE user_name=#{username1} //不需要手工加引号
</select>
<select id="queryUserListByName2" resultType="com.zpc.mybatis.pojo.User">
select * from tb_user WHERE user_name='${username2}' //需要手动加了引号
</select>
6.4.3 resultMap结果映射
resultMap
元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets
数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap
能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
上面的例子中,已经见过简单映射语句的示例,它们没有显式指定 resultMap
, 而是使用了resultType
。
使用resultMap可以解决两大问题:
- POJO类属性名与表结构字段名不一致的问题(即使转换为标准的驼峰格式也不一定能全部解决)
- 完成高级查询映射,比如说:一对一、一对多,多对多。
6.4.3.1 字段名对应
在前文的例子中,我们使entity中的属性名,与表中的字段名严格一致,所以避免了属性名与表结构字段名不一致的问题。但实际上这种做法破坏了Java代码中的命名规范(通常是驼峰命名)。我们把com.zyp.mydemo.entity.Customer类改为符合驼峰命名的形式:
package com.zyp.mydemo.entity;
import java.time.LocalDateTime;
public class Customer {
private String customerId;
private String loginAccount;
private String loginEmail;
private String customerName;
private int customerLevel;
private String status;
private LocalDateTime createDatetime;
... 所有属性的getter()和setter()方法。 (通过Eclipse自动生成) ....
... toString()方法。 (通过Eclipse自动生成) ....
}
这时运行就会报错:
Customer [customerId=null, loginAccount=null, loginEmail=null, customerName=null, customerLevel=0, status=E, createDatetime=null]
org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'login_account' in 'class com.zyp.mydemo.entity.Customer'
### The error may exist in mappers/CustomerMapper.xml
解决方法一(开启驼峰格式自动转换)
可以在mybatis-config.xml全局配置文件中增加以下设置,使其自动转换为驼峰格式:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
另外:在Mapper.xml文件中,updateOne里面的SQL语句指定的Java变量也需要改为符合驼峰格式,避免提示错误:找不到entity类中的属性。
解决方法二(在SQL语句中为每个字段设置别名,使其与Java类的属性名一致)
(过程略)
解决方法三:(使用resultMap)
在以上情况下,在xml中使用了resultType指定。但MyBatis 会在幕后自动创建一个 ResultMap
,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。
你会发现上面的例子没有一个需要显式配置 ResultMap
,这就是 ResultMap
的优秀之处——你完全可以不用显式地配置它们。 虽然上面的例子不用显式配置 ResultMap
。 但为了讲解,我们来看看如果在刚刚的示例中,显式使用外部的 resultMap
会怎样,这也是解决列名不匹配的另外一种方式。
修改后的CustomerMapper.xml文件如下(增加了CustomerResultMap定义,并把resultType
换成了 resultMap
)
<?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应等于对于Dao的Java接口全名。这样就不用人工写Dao接口的实现类。Mybatis会通过动态代理自动生成实现类 -->
<mapper namespace="com.zyp.mydemo.dao.CustomerMapper">
<resultMap id="CustomerResultMap" type="Customer">
<id property="customerId" column="customer_ID" />
<result property="loginAccount" column="login_account" />
<result property="loginEmail" column="login_email" />
<result property="customerName" column="customer_Name" />
<result property="customerLevel" column="customer_level" />
<result property="status" column="status" />
<result property="createDatetime" column="create_datetime" />
</resultMap>
<!-- 这里的语句id必须与Dao接口中的方法名一致。这样就不用人工写Dao接口的实现类 -->
<select id="findOneByID" resultMap="CustomerResultMap">
select customer_ID,
login_account, login_email, customer_Name,
customer_level, status,
create_datetime
from Customer where customer_ID = #{c_id}
</select>
<!-- 这里的语句id必须与Dao接口中的方法名一致。这样就不用人工写Dao接口的实现类 -->
<select id="findAll" resultMap="CustomerResultMap">
select customer_ID,
login_account, login_email, customer_Name,
customer_level, status,
create_datetime
from Customer
</select>
<!-- 这里的语句id必须与Dao接口中的方法名一致。这样就不用人工写Dao接口的实现类 -->
<update id="updateOne" parameterType="Customer">
UPDATE Customer
<set>
<if test="loginAccount != null">login_account = #{loginAccount},</if>
<if test="loginEmail != null">login_email = #{loginEmail},</if>
<if test="customerName != null">customer_Name = #{customerName},</if>
<if test="customerLevel != null">customer_level = #{customerLevel},</if>
<if test="status != null">status = #{status},</if>
<if test="createDatetime != null">create_datetime = #{createDatetime}</if>
</set>
WHERE
(customer_ID = #{customerId});
</update>
</mapper>
6.4.3.2 高级映射
MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。
比如,我们如何映射下面这个语句?
<!-- 非常复杂的语句(多张表关联) -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Post P on B.id = P.blog_id
left outer join Comment C on P.id = C.post_id
left outer join Post_Tag PT on PT.post_id = P.id
left outer join Tag T on PT.tag_id = T.id
where B.id = #{id}
</select>
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 不用紧张,我们会一步一步地来说明。虽然它看起来令人望而生畏,但其实非常简单。
<!-- 定义了一个:复杂结果的resultMap映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
以下内容摘自:https://blog.csdn.net/hellozpc/article/details/80878563 中“高级查询”一节。
比如有,多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
Order类:
public class Order {
private Integer id;
private Long userId;
private String orderNumber;
private Date created;
private Date updated;
private User user;
private List<OrderDetail> detailList;
}
OrderDetail类:
public class OrderDetail {
private Integer id;
private Integer orderId;
private Double totalPrice;
private Integer status;
private Item item;
}
public class Item {
private Integer id;
private String itemName;
private Float itemPrice;
private String itemDetail;
}
Dao层接口:
/**
* 根据订单号查询订单用户的信息及订单详情及订单详情对应的商品信息
* @param number
* @return
*/
Order queryOrderWithUserAndDetailItemByOrderNumber(@Param("number") String number);
Mapper.xml配置:
<resultMap id="OrderUserDetailItemResultMap" type="com.zpc.mybatis.pojo.Order" autoMapping="true">
<id column="id" property="id"/>
<association property="user" javaType="com.zpc.mybatis.pojo.User" autoMapping="true">
<id column="user_id" property="id"/>
</association>
<collection property="detailList" javaType="List" ofType="com.zpc.mybatis.pojo.OrderDetail" autoMapping="true">
<id column="detail_id" property="id"/>
<association property="item" javaType="com.zpc.mybatis.pojo.Item" autoMapping="true">
<id column="item_id" property="id"/>
</association>
</collection>
</resultMap>
<select id="queryOrderWithUserAndDetailItemByOrderNumber" resultMap="OrderUserDetailItemResultMap">
select * ,od.id as detail_id from tb_order o
left join tb_user u on o.user_id=u.id
left join tb_orderdetail od on o.id=od.order_id
left join tb_item i on od.item_id=i.id
where o.order_number = #{number}
</select>
另外:resultMap的继承还支持继承(略)
高级查询的整理
resutlType无法帮助我们自动的去完成映射,所以只有使用resultMap手动的进行映射
type 结果集对应的数据类型 id 唯一标识,被引用的时候,进行指定
<resultMap type="Order" id="orderUserLazyResultMap">
<!—定义pojo中的单个对象的 property 定义对象的属性名, javaType 属性的类型,
<association property="user" javaType="User" autoMapping="true">
<id />
</association>
<!—如果属性是集合使用collection ,javaType 集合的类型,ofType 表示集中的存储的元素类型
<collection property="details" javaType="List" ofType="OrderDetail" autoMapping="true">
<id />
</resultMap>
6.5 缓存
6.5.1 一级缓存
Mybatis的一级缓存的作用域session,当openSession()后,如果执行相同的SQL(相同的语句和参数),Mybatis不会真正将SQL发给数据库执行,而是直接从缓存中返回。 一级缓存机制默认是开启的(人工可以清空缓存,但无法关闭该机制)。 该缓存是会话级别的的。
1、一级缓存的生命周期有多长?
a、MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
2、怎么判断某两次查询是完全相同的查询?
一级缓存满足条件:
1、同一个session中
2、相同的SQL和参数
6.5.2 二级缓存
Mybatis 的二级缓存的作用域是一个mapper的namespace ,同一个namespace中查询sql可以从缓存中命中。 **该缓存是全局级别的的。**MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口。
开启二级缓存需要①在全局配置mybatis-config.xml文件设置cacheEnabled
属性(默认已经设置为true,所以可以不用显示设置),而在*Mapper.xml映射文件中增加cache标签(还可以具体设置其缓存属性,如下):
<mapper namespace="com.yihaomen.mybatis.dao.StudentMapper">
<!--开启本mapper的namespace下的二级缓存-->
<!--
eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
(1) LRU(默认),最近最少使用的,一处最长时间不用的对象
(2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
(3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
(4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,
移除最长时间不用的对形象
flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象
readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,他的默认值是false,允许我们修改
-->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
(...以下内容略...)
如果我们配置了二级缓存就意味着:
- 映射语句文件中的所有select语句将会被缓存。
- 映射语句文件中的所欲insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
- 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
7 mybatis-generator代码生成工具
MyBatis Generator(MBG)是MyBatis 和iBATIS的代码生成器。可以生成简单CRUD操作的XML配置文件、Mapper文件(DAO接口)、实体类。实际开发中能够有效减少程序员的工作量,甚至不用程序员手动写sql。
通常可以先用此工具生成,如有不满足需求的,再人工修改。
具体使用方式和步骤略。
8 Mybatis与Spring整合
Spring和Mybatis各自是独立的,由于历史原因。Spring并没有把mybatis集成到Spring的内部。因此处理Mybatis本身外,还需要一个桥接将两者无缝整合在一起。这个就是MyBatis-Spring
。 MyBatis-Spring
是MyBatis 的一个社区子项目,实现Mybatis对 Spring 的集成。
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession
并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException
。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要——因为本手册中不会提供二者的基本内容,安装和配置教程。
MyBatis-Spring 需要以下版本:
MyBatis-Spring | MyBatis | Spring 框架 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
8.1 配置依赖
对于Maven工程,在pom.xml
文件已有内容的基础上增加以下内容。其中mybatis-spring是必须的,其他的为可选附加(通常在项目中经常出现):
<!--MyBatis-Spring,相当于实现了spring与mybatis的桥接-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<!--数据库连接池,这里使用阿里的druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--spring容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
8.2 Spring的配置文件
在src/main/resources目录下,增加applicationContext_db.xml文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 加载外部配置文件 -->
<context:property-placeholder location="classpath:*.properties"/>
<!-- 数据库连接池(这里使用阿里的连接池) -->
<bean id="dataSource_druid" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${datasource.driver}"/>
<property name="url" value="${datasource.url}"/>
<property name="username" value="${datasource.username}"/>
<property name="password" value="${datasource.password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${datasource.initialSize}"></property>
<!-- 连接池最大数据库连接数 0 为没有限制 -->
<property name="maxActive" value="${datasource.maxActive}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${datasource.minIdle}"></property>
<!--最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制-->
<property name="maxWait" value="${datasource.maxWait}"></property>
</bean>
<!-- spring和MyBatis完美整合 -->
<!-- 在标准的MyBatis用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。 而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。 "org.mybatis.spring"开头的Java对象是属于mybatis-spring的,不是标准的Mybatis中的 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource_druid"/>
<property name="typeAliasesPackage" value="com.zyp.mydemo.entity" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/zyp/mydemo./dao/*.xml"></property>
<!--如果mybatis-config.xml中没有特殊配置也可以不需要下面的配置-->
<!-- <property name="configLocation" value="classpath:mybatis-config.xml" /> -->
</bean>
<!-- "org.mybatis.spring"开头的Java对象,是属于mybatis-spring的,不是标准的Mybatis中的 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- DAO接口所在包名,Spring会自动查找其下的类。 扫描后,把他加入到Spring的Bean中 -->
<property name="basePackage" value="com.zyp.mydemo.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager (这里使用了JDBC事务) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource_druid"/>
</bean>
</beans>
在src/main/resources目录下(与applicationContext_db.xml文件同目录),创建db.properties文件:
datasource.driver=com.mysql.cj.jdbc.Driver
datasource.url=jdbc:mysql://192.168.43.201:3306/db_seckill
datasource.username=root
datasource.password=Pwd_1234
datasource.initialSize=5
datasource.maxActive=20
datasource.minIdle=3
datasource.maxWait=1000
8.3 测试
增加一个新的测试类:com.zyp.mydemo.Test_Spring:
package com.zyp.mydemo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.zyp.mydemo.dao.CustomerMapper;
import com.zyp.mydemo.entity.Customer;
public class Test_Spring {
public static void main(String[] args) {
try {
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"applicationContext_db.xml"});
CustomerMapper customerMapper = context.getBean(CustomerMapper.class);
Customer customer = customerMapper.findOneByID("CID2000000000003");
System.out.println("结果:\n" + customer);
customer.setLoginEmail("new_email@11.com");
customerMapper.updateOne(customer);
Customer customer2_new = customerMapper.findOneByID("CID2000000000003");
System.out.println("结果2(更新后):\n" + customer2_new);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行测试结果:
六月 07, 2020 2:59:55 下午 com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl info
信息: {dataSource-1} inited
结果:
Customer [customerId=CID2000000000003, loginAccount=LOGIN2000000000003, loginEmail=13300000003@163.com, customerName=NAME2000000000003, customerLevel=10, status=E, createDatetime=2020-06-07T00:32:26]
结果2(更新后):
Customer [customerId=CID2000000000003, loginAccount=LOGIN2000000000003, loginEmail=new_email@11.com, customerName=NAME2000000000003, customerLevel=10, status=E, createDatetime=2020-06-06T11:32:26]
可以看到:
- 由于Spring容器进行了Bean管理,我们不用自己构造sqlSession,直接从容器获得Dao层的对象使用即可。
- 通过
MyBatis-Spring
全局配置文件可以整合到Spring的xml配置文件中。
8.4 工程结构
此时的工程结构为:
9 Mybatis与Spring Boot整合
The MyBatis-Spring-Boot-Starter
是单独的一个Starter,可以在Spring Boot 基础上快速的构建Mybatis应用.
通过使用此模块,您将实现:
- 构建独立的应用程序
- 将样板减少到几乎为零
- 较少的XML配置
MyBatis-Spring-Boot-Starter
需要以下版本:
MyBatis-Spring-Boot-Starter | MyBatis-Spring | Spring Boot | Java |
---|---|---|---|
2.1 | 2.0 (need 2.0.2+ for enable all features) | 2.1 or higher | 8 or higher |
9.1 新建Spring Boot 工程
使用Spring Boot Initialize (https://start.spring.io), 新建一个全新工程。 这里使用了Idea IDE,使用Eclipse类似。
新建工程时,勾选“Mybatis”、“MySQL”、“Druid”等。 为了简便,我们还引入了lombok, 直接使用@Data
注解避免了人工编写大量的getter/setter和toString等方法。 为了测试方便,还选择了web。
全新工程生成完毕后,可以看到pom.xml
文件中有以下内容:
... 其他省略...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<!-- 存放资源的目录(路径相对pom.xml的相对路径)。 这样maven在build时会自动将这些资源也拷贝到target对应的目录下 -->
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
注意
:在pom.xml
文件中增加了resources的内容。
因为Maven如果没有进行特殊的配置,Maven会按照标准的目录结构查找和处理各种类型文件:src/main/java和src/test/java 这两个目录中的所有*.java文件会分别在comile和test-comiple阶段被编译,并将编译结果.class文件分别放到了target/classes
和targe/test-classes
目录中,但是这两个目录中的其他文件都会被忽略掉。
因此需要在pom.xml
文件中指定resource,告诉Maven指定的资源也需要从src/main下复制到target/classes
和targe/test-classes
目录中。
修改pom.xml后,记得使其生效(在Idea中,右键pom.xml文件→Maven→reimport)
9.2 配置application.yml
默认的application.properties(内容默认为空),将文件名改为“application.yml”。然后在其中增加以下配置:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.43.201:3306/db_seckill
username: root
password: Pwd_1234
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
mybatis:
# 扫描Mapper接口对应的xml文件。
mapper-locations: classpath:com/zyp/seckill/dao/*Mapper.xml
# 类型别名,这样在*Mapper.xml文件中,可以不用写类全名。
type-aliases-package: com.zyp.seckill.entity
9.3 创建entity代码
com.zyp.seckill.entity.Customer:
package com.zyp.seckill.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data // 这是lombok注解
public class Customer {
private String customer_ID;
private String login_account;
private String login_phone;
private String login_email;
private String customer_Name;
private int customer_level;
private String status;
private LocalDateTime create_datetime;
}
9.4 创建Dao层
com.zyp.seckill.dao.CustomerMapper:
package com.zyp.seckill.dao;
import com.zyp.seckill.entity.Customer;
import java.util.List;
//@Component // 是否加这个注解对运行无影响。如果没有此注解,只是在Idea IDE中需要@Autowired注入本对象是,Idea中提示错误。
public interface CustomerMapper {
public Customer selectByID(String id);
public List<Customer> findAll();
}
在com.zyp.seckill.dao路径下,新建CustomerMapper.xml文件(xml的文件名与Dao层放在同目录下,且文件名相同)
<?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.zyp.seckill.dao.CustomerMapper">
<select id="selectByID" resultType="Customer">
SELECT * FROM Customer where customer_ID = #{id};
</select>
<select id="findAll" resultType="Customer">
SELECT * FROM Customer;
</select>
</mapper>
9.5 在Boot启动类中增加扫描MapperScan注解
package com.zyp.seckill;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.zyp.seckill.dao") //使用MapperScan批量扫描所有的Dao层接口,并自动生成实现类。
public class SeckillApplication {
public static void main(String[] args) {
SpringApplication.run(SeckillApplication.class, args);
}
}
9.6 测试
本工程有spring-boot-starter-web
支持Spring MVC,可通过Web访问来进行测试,构建测试类com.zyp.seckill.controller.HelloController
package com.zyp.seckill.controller;
import com.zyp.seckill.dao.CustomerMapper;
import com.zyp.seckill.entity.Customer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class HelloController {
Logger logger = LoggerFactory.getLogger(HelloController.class);
@Autowired
CustomerMapper customerMapper; // 由Spring自动注入
@RequestMapping(value = "getOneCustomer/{id}", method = RequestMethod.GET)
public Customer getOneCustomer(@PathVariable("id") String id) {
logger.info("This is getOneCustomer has been called");
return customerMapper.selectByID(id);
}
@RequestMapping(value = "getAllCustomer", method = RequestMethod.GET)
public List<Customer> getAllCustomer() {
logger.info("This is getAllCustomer has been called");
return customerMapper.findAll();
}
}
启动Spring Boot 类后。在浏览器中访问:http://localhost:8080/getAllCustomer 结果如下
可以看到:
- 由于Spring容器进行了Bean管理,我们不用自己构造sqlSession,直接从容器获得Dao层的对象使用即可。
- 通过
MyBatis-Spring
全局配置文件可以整合到Spring Boot的yml配置文件中,无需单独Mybatis的全局配置文件