前言
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
— 以上内容来源 MyBatis官网
说白了MyBatis 就是一个半自动的持久层框架,相对于SpringDataJap 和 Hibernate 它的使用更加灵活,对于复杂业务系统更推荐使用 MyBatis。
本文主要简单介绍如何基于SpringBoot 2 使用 Mybatis,通过本文你将了解到如下内容:
- SpringBoot 注解方式 进行增删改查
- SpringBoot xml方式 进行增删改查
阅读前需要你必须了解如何搭建 SpringBoot 项目,并且会简单使用 MySql 数据库相关操作。
接下来就开始我们 Demo 实战操作!
SpringBoot 使用MyBaties实战演示
在介绍Demo 实战之前,先带大家了解一下 MyBaties官方提供 SpringBoot 关于注解方式和 XML 方式 2个版本的 Demo。你可以把 github 上的 Samples 代码 clone 下来进行查阅
代码地址: https://github.com/mybatis/spring-boot-starter
官网提供的案例相对比较简单,于是自己对于经常使用的操作完善了一下。闲话少说,接下来直接开整。
注解方式
注解配置和初始化数据
这里开始写注解版的 Demo, 通过SpirngBoot 使用MyBaties 演示一个商品管理的增删改查。
第一步:引入Mybatis 的 starter 依赖和 Mysql 数据库驱动的依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步:创建商品的实体类
/**
* 商品的实体类
* @author lijunkui
*/
public class Product {
private Long id;
/**商品名称*/
private String productName;
/**商品价格*/
private Double price;
/**商品简介*/
private String productBrief;
//省略get and set方法
}
第三步:在application.properties 配置文件中 配置数据库信息。
spring.datasource.url=jdbc:mysql://localhost:3306/learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
第四步:创建商品表的 sql
CREATE TABLE `product` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '商品id',
`product_Name` varchar(25) DEFAULT NULL COMMENT '商品名称',
`price` decimal(8,3) DEFAULT NULL COMMENT '价格',
`product_Brief` varchar(125) DEFAULT NULL COMMENT '商品简介',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert into `product`(`product_Name`,`price`,`product_Brief`) values ('苹果','20.000','好吃的苹果,红富士大苹果');
到这里基础步骤介绍完毕,接下来就是具体的操作CRUD 操作。
通过Id查询
创建商品的Mapper类 并添加根据id查询功能
@Mapper
public interface ProductMapper {
@Results(id="product" ,value= {
@Result(property = "id", column = "id", id = true),
@Result(property = "productName", column = "product_Name"),
@Result(property = "price", column = "price"),
@Result(property = "productBrief", column = "product_Brief")
})
@Select("select * from product where id = #{id}")
public Product findById(@Param("id") Long id);
}
创建查询测试用例:
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductMapperTest {
@Autowired
private ProductMapper productMapper;
@SuppressWarnings("deprecation")
@Test
public void findById() {
Product product = productMapper.findById(1l);
Assert.assertNotNull(product);
}
}
测试结果:
动态查询
定义条件查询功能
通过@SelectProvider 的type属性指定定义查询条件逻辑的Provider类
@Mapper
public interface ProductMapper {
@SelectProvider(type = ProductProvider.class, method = "findByCondition")
public List<Product> findByCondition(Product product);
}
条件逻辑的Provider类 通过 new SQL对象编写条件查询逻辑
public class ProductProvider {
public String findByCondition(Product product) {
return new SQL() {{
SELECT("id,product_Name as productName ,price,product_Brief as productBrief");
FROM("product");
if (!StringUtils.isEmpty(product.getProductName())) {
WHERE("product_Name like CONCAT('%',#{productName},'%')");
}
if (product.getPrice()!=null) {
WHERE("price = #{price} ");
}
}}.toString();
}
}
动态添加
创建添加商品功能
/**
* @param product
* @return Long
*/
@Insert("insert into product (product_Name, price,product_Brief)
values(#{productName}, #{price}, #{productBrief})")
@Options(useGeneratedKeys=true,keyProperty="id")
@SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty =
"id", before = false, resultType = long.class)
public Long insert(Product product);
修改
创建修改商品功能
/**
* 修改商品
* @param product
*/
@Update("update product set product_Name=#{productName} , price= #{price} , product_Brief = #{productBrief} where id=#{id}")
public void update(Product product);
动态修改
创建动态修改商品功能
通过@UpdateProvider的type属性指定动态修改逻辑的Provider类
@UpdateProvider(type = ProductProvider.class, method =
"updateDynamic")
public void updateDynamic(Product product);
和条件查询Provider类一样我们通过 new SQL编写动态修改逻辑。
public class ProductProvider {
public String updateDynamic(Product product) {
return new SQL() {{
UPDATE("product");
if (!StringUtils.isEmpty(product.getProductName())) {
SET("product_Name = #{productName}");
}
if (product.getPrice()!=null) {
SET("price = #{price}");
}
if(!StringUtils.isEmpty(product.getProductBrief()))
SET("product_Brief = #{productBrief}");
}}.toString();
}
}
删除
创建删除商品功能
@Delete("delete from product where id=#{id}")
public void deleteById(long id);
商品管理完整的测试用例
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductMapperTest {
@Autowired
private ProductMapper productMapper;
@SuppressWarnings("deprecation")
@Test
public void findById() {
Product product = productMapper.findById(1l);
Assert.assertNotNull(product);
}
@SuppressWarnings("deprecation")
@Test
public void findByCondition() {
Product product = new Product();
product.setProductName("蕉");
List<Product> findByCondition = productMapper.findByCondition(product);
Assert.assertTrue(findByCondition.size()>0);
}
@SuppressWarnings("deprecation")
@Test
public void insert() {
Product product = new Product();
product.setProductName("香蕉");
product.setPrice(45d);
product.setProductBrief("好吃的香蕉!");
Long insert = productMapper.insert(product);
Assert.assertTrue(insert > 0 );
}
@Test
public void update() {
Product product = new Product();
product.setId(3l);
product.setProductName("香蕉3");
product.setPrice(45d);
product.setProductBrief("好吃的香蕉!");
productMapper.update(product);
}
@Test
public void updateDynamic() {
Product product = new Product();
product.setId(4l);
product.setProductName("香蕉4");
productMapper.updateDynamic(product);
}
@Test
public void deleteById() {
productMapper.deleteById(4l);
}
}
XML方式
XML配置和初始化数据
XMl的方式 通过 Hotel 的增删改查功能进行演示。
Hotel 表sql 和测试数据
CREATE TABLE `hotel` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '旅馆id',
`city` varchar(125) DEFAULT NULL COMMENT '城市',
`name` varchar(125) DEFAULT NULL COMMENT '旅馆名称',
`address` varchar(256) DEFAULT NULL COMMENT '旅馆地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `hotel`(`city`,`name`,`address`) VALUES ( '北京','汉庭','朝阳区富明路112号');
创建Hotle 实体 其中包含 旅馆id 城市 旅馆名称 旅馆地址 成员属性
public class Hotel {
private Long id;
private String city;
private String name;
private String address;
}
通过Id查询
创建Mapper类映射的xml HotelMapper.xml namespace 是 HotelMapper 的包路径地址。
然后在创建通过 Id 查询 sql 配置。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.lijunkui.mybaties.mapper.HotelMapper">
<select id="selectByCityId" resultType="Hotel">
select * from hotel where id = #{id}
</select>
</mapper>
创建mybatis-config.xml 并且设置HotelMapper.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>
<package name="cn.lijunkui.mybaties.domain"/>
</typeAliases>
<mappers>
<mapper resource="mybaties/mapper/HotelMapper.xml"/>
</mappers>
</configuration>
配置文件目录地址如下图:
在Hotel 的Mapper类中创建通过 Id 查询 selectByCityId 方法。
@Mapper
public interface HotelMapper {
Hotel selectByCityId(long id);
}
需要注意的是 方法的名称要和HotelMapper.xml 通过 Id 查询 sql 配置 id一致。
编写测试用例:
@SpringBootTest
@RunWith(SpringRunner.class)
public class HotelMapperTest {
@Autowired
private HotelMapper hotelMapper;
@SuppressWarnings("deprecation")
@Test
public void selectByCityId() {
Hotel result = hotelMapper.selectByCityId(1l);
Assert.assertNotNull(result);
}
}
查询所有
HotelMapper.xml 中的配置
<resultMap id="HotelResultMap" type="cn.lijunkui.mybaties.xml.domain.Hotel" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="city" property="city" jdbcType="VARCHAR" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="address" property="address" jdbcType="VARCHAR" />
</resultMap>
<sql id="baseSelect">
select id, city, name, address from hotel
</sql>
<select id="selectHotelList" parameterType="cn.lijunkui.mybaties.xml.domain.Hotel" resultMap="HotelResultMap">
<include refid="baseSelect"/>
<where>
<if test="id != null "> and id = #{id}</if>
<if test="city != null and city != '' "> and city = #{city}</if>
<if test="name != null and name != '' "> and name = #{name}</if>
<if test="address != null and address != '' "> and address = #
{address}</if>
</where>
</select>
我们需要在HotelMapper.xml 配置resultMap (返回参数的结果集)
sql 定义一些公共的sql 代码
HotelMapper 类中创建查询所有的方法
@Mapper
public interface HotelMapper {
List<Hotel> selectHotelList(Hotel hotel);
}
分页查询
HotelMapper.xml 中的配置
<select id="selectHotelListByPage" resultMap="HotelResultMap" parameterType="cn.lijunkui.mybaties.xml.param.HotalParam">
<include refid="baseSelect" />
<where>
<if test="id != null "> and id = #{id}</if>
<if test="city != null and city != '' "> and city = #{city}</if>
<if test="name != null and name != '' "> and name = #{name}</if>
<if test="address != null and address != '' "> and address = #{address}</if>
</where>
limit #{startIndex} , #{pageSize}
</select>
分页的查询条件和返回结果基础封装类
public class PageInfo {
private Integer pageSize = 10;
private Integer currentPage= 1;
private Integer startIndex;
public Integer getStartIndex() {
this.startIndex = (currentPage-1) * pageSize;
return startIndex;
}
}
Hotal 查询条件数据封装类
public class HotalParam extends PageInfo{
private Long id;
private String city;
private String name;
private String address;
}
分页的返回结果封装类
public class Page<T> extends PageInfo {
private Integer totalCount;
private List<T> items;
}
HotelMapper 类中创建查询所有的方法
@Mapper
public interface HotelMapper {
List<Hotel> selectHotelListByPage(HotalParam hotalParam);
}
分页查询测试类:
@Test
public void selectHotelListByPage(){
//查询第一页数据
HotalParam hotalParam = new HotalParam();
List<Hotel> hotelList = hotelMapper.selectHotelListByPage(hotalParam);
List<Hotel> allHotel = hotelMapper.selectHotelList(new Hotel());
Page<Hotel> page = new Page();
page.setItems(hotelList);
page.setPageSize(hotalParam.getPageSize());
page.setCurrentPage(hotalParam.getCurrentPage());
page.setTotalCount(allHotel.size());
System.out.println("------------------------------------");
//查询第二页数据
HotalParam hotalParam2 = new HotalParam();
hotalParam2.setCurrentPage(2);
List<Hotel> hotelList2 = hotelMapper.selectHotelListByPage(hotalParam2);
System.out.println("------------------------------------");
}
动态修改
HotelMapper.xml 中的配置
<update id="update" parameterType="cn.lijunkui.mybaties.xml.domain.Hotel">
update hotel
<trim prefix="SET" suffixOverrides=",">
<if test="city != null and city != '' ">city = #{city},</if>
<if test="name != null and name != '' ">name = #{name},</if>
<if test="address != null and address != '' ">address = #{address},</if>
</trim>
where id = #{id}
</update>
HotelMapper 类中创建动态修改的方法
@Mapper
public interface HotelMapper {
void update(Hotel hotel);
}
删除
HotelMapper.xml 中的配置
<delete id="deleteById" parameterType="Long">
delete from hotel where id = #{id}
</delete>
HotelMapper 类中创建删除方法
@Mapper
public interface HotelMapper {
void deleteById(long id);
}
批量删除
HotelMapper.xml 中的配置
<delete id="deleteByIds" parameterType="Long">
delete from hotel where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
HotelMapper 类中创建批量删除方法
@Mapper
public interface HotelMapper {
void deleteByIds(Long[] ids);
}
小结
SpringBoot XML 和 注解的方式可以根据个人的喜好自行选择。不过个人还是推荐使用 XML的方式,因为注解的方式如果有改动的话还需要在去修改DAO类。如果你还没有在SpringBoot 上操作过Mybatis 还等什么抓紧根据本文操作一遍吧。
代码示例
我本地环境如下:
- SpringBoot Version: 2.1.0.RELEASE
- Apache Maven Version: 3.6.0
- Java Version: 1.8.0_144
- IDEA:Spring Tools Suite (STS)
整合过程如出现问题可以在我的GitHub 仓库 springbootexamples 中模块名为 spring-boot-2.x-mybaties 项目中进行对比查看
GitHub:https://github.com/zhuoqianmingyue/springbootexamples
参考文献
- http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
- http://www.mybatis.org/mybatis-3/java-api.html