Mybatis 是一个基于 JDBC 实现的,支持普通 SQL 查询、存储过程和高级映射的优秀持久层框架,去掉了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。Mybatis 主要思想是将程序中大量的 SQL 语句剥离出来,配置在配置文件中,以实现 SQL 的灵活配置。
1.Mybatis的使用
首先在 MySQL 数据库创建表:
CREATE TABLE `product_info` (
`product_id` bigint(18) unsigned NOT NULL COMMENT '商品id, 主键',
`product_name` varchar(32) NOT NULL COMMENT '商品名称',
`product_price` decimal(8,2) unsigned NOT NULL DEFAULT '99999.99' COMMENT '商品单价',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用 Mybatis 需要添加 Maven 依赖:
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
然后通过 application.properties 中的 spring.datasource.* 前缀配置属性:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_test?characterEncoding=utf8&useSSL=false
spring.datasource.username=mysql
spring.datasource.password=123456
# 开启调试模式,打印sql
#logging.level.com.example.server.soa.dao.mysql.mapper=DEBUG
并新建 domain 对象:
public class ProductInfo {
private Long productId;
private String productName;
private BigDecimal productPrice;
private Date gmtCreate;
private Date gmtModified;
// 省略getter、setter方法
}
Mybatis 有 xml 和注解两种开发方式,推荐使用 xml 开发方式。
1.Mybatis xml开发方式
xml 版本保持了映射文件的老传统,系统会自动根据方法名在映射文件中找对应的 sql。在 application.properties 中新增 mybatis.* 前缀配置:
mybatis.config-location=classpath:mybatis-config.xml
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
其中 resources/mybatis-config.xml 中可以以传统 xml 的方式配置 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>
</configuration>
在 resources/mapper 目录下添加 ProductInfoMapper.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.example.server.soa.dao.mysql.mapper.ProductInfoMapper">
<resultMap id="BaseResultMap" type="com.example.server.soa.dao.mysql.domain.ProductInfo">
<id column="product_id" jdbcType="BIGINT" property="productId" />
<result column="product_name" jdbcType="VARCHAR" property="productName" />
<result column="product_price" jdbcType="DECIMAL" property="productPrice" />
<result column="gmt_create" jdbcType="TIMESTAMP" property="gmtCreate" />
<result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified" />
</resultMap>
<sql id="Base_Column_List">
product_id, product_name, product_price, gmt_create, gmt_modified
</sql>
<select id="select" parameterType="java.lang.Long" resultMap="BaseResultMap" >
select
<include refid="Base_Column_List" />
from product_info
where product_id = #{productId,jdbcType=BIGINT}
</select>
<select id="selectAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from product_info
</select>
<delete id="delete" parameterType="java.lang.Long" >
delete from
product_info
where
product_id =#{productId}
</delete>
<insert id="insert" parameterType="com.example.server.soa.dao.mysql.domain.ProductInfo">
insert into product_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="productName != null">
product_name,
</if>
<if test="productPrice != null">
product_price,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="productName != null">
#{productName,jdbcType=VARCHAR},
</if>
<if test="productPrice != null">
#{productPrice,jdbcType=DECIMAL},
</if>
</trim>
</insert>
<update id="update" parameterType="com.example.server.soa.dao.mysql.domain.ProductInfo">
update product_info
<set>
<if test="productName != null">
product_name = #{productName,jdbcType=VARCHAR},
</if>
<if test="productPrice != null">
product_price = #{productPrice,jdbcType=DECIMAL},
</if>
</set>
where product_id = #{productId,jdbcType=BIGINT}
</update>
</mapper>
然后编写 dao 层的 Java 代码:
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductInfoMapper {
ProductInfo select(Long productId);
List<ProductInfo> selectAll();
int delete(Long productId);
int insert(ProductInfo productInfo);
int update(ProductInfo productInfo);
}
这样就完成了 dao 层的开发,使用的时候当作普通的类注入就可以了:
@Resource
private ProductInfoMapper productInfoMapper;
2.Mybatis注解开发方式
这种版本无需编写 xml,直接编写 mapper 类即可:
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductInfoMapper {
@Select("SELECT * FROM product_info WHERE product_id =#{productId,jdbcType=BIGINT}")
@Results({
@Result(property = "productId", column = "product_id"),
@Result(property = "productName", column = "product_name"),
@Result(property = "productPrice", column = "product_price"),
@Result(property = "gmtCreate", column = "gmt_create"),
@Result(property = "gmtModified", column = "gmt_modified")
})
ProductInfo select(Long productId);
@Select("SELECT * FROM product_info")
@Results({
@Result(property = "productId", column = "product_id"),
@Result(property = "productName", column = "product_name"),
@Result(property = "productPrice", column = "product_price"),
@Result(property = "gmtCreate", column = "gmt_create"),
@Result(property = "gmtModified", column = "gmt_modified")
})
List<ProductInfo> selectAll();
@Delete("DELETE FROM product_info WHERE product_id =#{productId,jdbcType=BIGINT}")
int delete(Long productId);
@Insert("INSERT INTO product_info(product_id,product_name,product_price) VALUES(#{productId,jdbcType=BIGINT},#{productName,jdbcType=VARCHAR},#{productPrice,jdbcType=DECIMAL})")
int insert(ProductInfo productInfo);
@UpdateProvider(type = ProductSqlBuilder.class, method = "buildUpdate")
int update(ProductInfo productInfo);
class ProductSqlBuilder {
public String buildUpdate (final ProductInfo productInfo) {
return new SQL(){{
UPDATE("product_info");
if (productInfo.getProductName() != null) {
SET("product_name=#{productName,jdbcType=VARCHAR}");
}
if (productInfo.getProductPrice() != null) {
SET("product_price=#{productPrice,jdbcType=DECIMAL}");
}
WHERE("product_id =#{productId,jdbcType=BIGINT}");
}}.toString();
}
}
}
如果传入的属性为枚举,可以使用 javaType 来指定:
@Select("SELECT * FROM product_info")
@Results({
@Result(property = "productEnum", column = "product_enum", javaType = ProductEnum.class),
})
List<ProductInfo> selectAll();
编写 Mapper 一步到位,这样就完成了 dao 层的开发,使用方法和 xml 开发方式没有任何区别。
3.多数据源的支持
1、同源数据库的多源支持
创建一个 Spring 配置类,定义两个 DataSource 用来读取 application.properties 中的不同配置。如下例子中,主数据源配置为 spring.datasource.primary 开头的配置,第二数据源配置为 spring.datasource.secondary 开头的配置。这里给出 primary 的配置,secondary 的配置类似。
@Configuration
@MapperScan(basePackages = "com.example.server.soa.dao.mysql.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Qualifier("primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primarySqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/primary/*.xml"));
return bean.getObject();
}
@Bean(name = "primaryTransactionManager")
@Primary
public DataSourceTransactionManager testTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "primarySqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
最关键的地方就是这块了,一层一层注入,再创建 DataSource,再创建 SqlSessionFactory,再创建事务,最后包装到 SqlSessionTemplate 中。其中需要制定分库的 mapper 文件地址以及分库 Dao 层代码。对应的 application.properties 配置如下:
spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.primary.url=jdbc:mysql://localhost:3306/spring_test?characterEncoding=utf8&useSSL=false
spring.datasource.primary.username=mysql
spring.datasource.primary.password=123456
spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.secondary.url=jdbc:mysql://localhost:3306/spring_test2?characterEncoding=utf8&useSSL=false
spring.datasource.secondary.username=mysql
spring.datasource.secondary.password=123456
然后 Mybatis 操作同源不同地址的数据库,只需要在对应的目录下编写代码即可。
Common Mapper 可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。支持单表操作,不支持通用的多表联合查询。分页插件 PageHelper 支持任何复杂的单表、多表分页。这两个开源项目的作者是同一个人,所以这里放到一起说,在使用上两者没有任何依赖关系。
2.Common Mapper的使用
首先在 MySQL 数据库创建表:
CREATE TABLE `product_info` (
`product_id` bigint(18) unsigned NOT NULL COMMENT '商品id, 主键',
`product_name` varchar(32) NOT NULL COMMENT '商品名称',
`product_price` decimal(8,2) unsigned NOT NULL DEFAULT '99999.99' COMMENT '商品单价',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用 Common Mapper 需要添加 Maven 依赖:
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.5</version>
</dependency>
<!-- common mapper -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
然后通过 application.properties 中的 spring.datasource.* 前缀配置属性:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_test?characterEncoding=utf8&useSSL=false
spring.datasource.username=mysql
spring.datasource.password=123456
# 开启调试模式,打印sql
#logging.level.com.example.server.soa.dao.mysql.mapper=DEBUG
并新建 domain 对象:
import javax.persistence.Id;
public class ProductInfo {
@Id
private Long productId;
private String productName;
private BigDecimal productPrice;
private Date gmtCreate;
private Date gmtModified;
// 省略getter、setter方法
}
这里需要注意的是为主键字段标记 @Id 注解,否则当你使用带有 ByPrimaryKey 的方法时,所有的字段会作为联合主键来使用。
下来我们编写 dao 层的 Java 代码,只需要继承通用 Mapper 接口即可:
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ProductInfoMapper extends tk.mybatis.mapper.common.Mapper<ProductInfo> {
}
通用 Mapper 提供了大量的通用接口:
selectOne
select
selectAll
selectCount
selectByPrimaryKey
方法太多,省略其他...
这样就完成了 dao 层的开发,使用的时候当作普通的类注入就可以了:
@Resource
private ProductInfoMapper productInfoMapper;
3.PageHelper的使用
使用分页插件 PageHelper 需要添加 Maven 依赖:
<!-- mybatis物理分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
我们在 Service 层中调用 Mapper 时,即可无侵入的进行分页查询:
// 物理分页
PageHelper.startPage(pageNum, pageSize);
PageHelper.orderBy("product_id desc");
List<ProductInfo> productInfoList = productInfoMapper.selectAll();
productInfoList 即是分页查询后的结果。其中 productInfoList 表面上是 List 类型,实际上是 Page (extends ArrayList) 类型。在 productInfoList 没有经过处理直接返回的情况下,我们就可以在 Controller 层获取 PageInfo 对象:
// 取分页后结果
PageInfo<ProductInfo> pageInfo = new PageInfo<ProductInfo>(productInfoList);
log.info("总记录数: {}", pageInfo.getTotal());
4.Mybatis原理
1、Mybatis 怎么把 xml 文件加载成 sql 执行的 ?
2、跟 jdbc 编译、预编译有什么区别?
3、连接池?
参考:http://mybatis.tk/
http://apidoc.gitee.com/free/Mapper/
http://github.com/abel533/Mapper/wiki
http://apidoc.gitee.com/free/Mybatis_PageHelper/
http://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
http://github.com/abel533/MyBatis-Spring-Boot