Spring(二) SpringBoot 集成 Mybatis

mybatis

使用 Mybatis 简化 JDBC 操作 中,简单描述了 Mybatis 的使用。这一篇主要记录下如何集成 Spring boot


Spring Boot 集成 Mybatis 简明过程

创建一个 Spring Initalizr 工程,依赖选择 web、MySQL、Mybatis

在application.properties填入以下内容

1
2
3
4
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/neu?characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=YourPassword
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  • 创建pojo包,创建 Student 实体类,跟数据库对应
1
2
3
4
5
6
7
8
public class Student {
Integer id;
String name;
String major;
Integer grade;

// 省略 getter setter
}
  • 创建mapper包,创建StudentMapper接口

在SpringBootApplication类中,添加@MapperScan("io.jerrysheh.student.mapper")注解,即可不用在 mapper 包下面的每一个接口都注解Mapper了。

1
2
3
4
5
6
@Mapper
public interface StudentMapper {

@Select("SELECT * FROM student")
List<Student> findAll();
}

如果有多个参数,用 @Param 注解

1
2
@Update("update user set password=#{password} WHERE id=#{id}")
void updatePassword(@Param("password") String password, @Param("id") Integer id);
  • 创建Controller包,创建StudentController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class StudentController {

@Autowired
StudentMapper studentMapper;

@GetMapping("/listStudent")
public void listStudent(){
List<Student> studentList = studentMapper.findAll();
for (Student student:
studentList) {
System.out.println(student.getName());
}
}
}

这样,运行后访问 127.0.0.1:8080/listStudent ,可看到控制台输出数据库查到的所有 student 名字。

xml 方式

有时候为了将SQL和Java代码隔离 ,会将 SQL 抽到 xml 里面,配置方法如下:

在application.properties填入以下内容(重要!!!)

1
2
3
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.jerrysheh.fun.entity
mybatis.configuration.map-underscore-to-camel-case=true

在 resources 目录下创建 mapper 文件夹,再创建 productmapper.xml 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.jerrysheh.fun.mapper.ProductMapper">

<select id="selectAll" resultType="com.jerrysheh.fun.entity.Product">
SELECT * FROM fun_product;
</select>

<insert id="addProduct" >
insert into fun_product(product_name, product_price) values (#{productName}, #{productPrice})
</insert>
</mapper>

注意:namespace一定要填写对应的 mapper 接口,不能只到 package

编写单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootTest
public class test {

@Autowired
ProductMapper productMapper;

@Test
public void test(){
List<Product> productList = productMapper.selectAll();
productList.forEach(System.out::println);
}

}

以下为Mybatis知识点

井字符和美元符的区别

#相当于对数据加上双引号,$相当于直接显示数据

#方式能够很大程度防止sql注入。$方式无法防止Sql注入。


动态SQL

虽然我们使用了注解,但是还是在注解接口CategoryMapper中使用了原生 SQL 语句。

1
2
@Insert(" insert into category_ ( name ) values (#{name}) ")
public int add(Category category);

其实,我们可以提供一个类,专门用来生成SQL语句

CategoryDynaSqlProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.jerrysheh.mapper;
import org.apache.ibatis.jdbc.SQL;

public class CategoryDynaSqlProvider {
public String list() {
return new SQL()
.SELECT("*")
.FROM("category_")
.toString();

}
public String get() {
return new SQL()
.SELECT("*")
.FROM("category_")
.WHERE("id=#{id}")
.toString();
}

public String add(){
return new SQL()
.INSERT_INTO("category_")
.VALUES("name", "#{name}")
.toString();
}
public String update(){
return new SQL()
.UPDATE("category_")
.SET("name=#{name}")
.WHERE("id=#{id}")
.toString();
}
public String delete(){
return new SQL()
.DELETE_FROM("category_")
.WHERE("id=#{id}")
.toString();
}
}

然后修改我们的CategoryMapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.jerrysheh.mapper;

import com.jerrysheh.pojo.Category;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface CategoryMapper {

@InsertProvider(type=CategoryDynaSqlProvider.class,method="add")
public int add(Category category);

@DeleteProvider(type=CategoryDynaSqlProvider.class,method="delete")
public void delete(int id);

@SelectProvider(type=CategoryDynaSqlProvider.class,method="get")
public Category get(int id);

@UpdateProvider(type=CategoryDynaSqlProvider.class,method="update")
public int update(Category category);

@SelectProvider(type=CategoryDynaSqlProvider.class,method="list")
public List<Category> list();
}

这样就可以动态生成SQL语句了

  • 注解中的 type= 填入我们的动态生成SQL类CategoryDynaSqlProvider.class
  • method=填入CategoryDynaSqlProvider类里的方法

@Results结果映射

如果 javabean 的属性字段 跟 数据库字段一一对应,名字保持一致,则直接可以:

1
2
@Select("select *from Demo where id=#{id}")  
public Demo selectById(int id);

但如果不对应,就要用@Result修饰返回的结果集,而@Results注解将指定的数据库列与指定JavaBean属性映射起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Select("SELECT * FROM `wx_message_config` WHERE `content_key_words` IS NOT NULL AND LENGTH(content_key_words) > 0")
@Results({
@Result(property = "msgType", column = "msg_type"),
@Result(property = "eventType", column = "event_type"),
@Result(property = "eventKey",column = "event_key"),
@Result(property = "contentKeyWords",column = "content_key_words")
})
List<WxMessageConfig> queryAllKeyWords();

@Select("SELECT * FROM `wx_message_config` WHERE `id` = #{id}")
@Results({
@Result(property = "msgType", column = "msg_type"),
@Result(property = "eventType", column = "event_type"),
@Result(property = "eventKey",column = "event_key"),
@Result(property = "contentKeyWords",column = "content_key_words")
})
WxMessageConfig queryKwById(int id);

这样会导致写很多重复内容,可以用 @ResultMap(“id”)

1
2
3
4
5
6
7
8
9
@Select("SELECT id, name, password FROM user WHERE id = #{id}")
@Results(id = "userMap", value = { @Result(column = "id", property = "id", javaType = Integer.class),
@Result(column = "name", property = "name", javaType = String.class),
@Result(column = "password", property = "password", javaType = String.class) })
User findById(Integer id);

@Select("SELECT * FROM user")
@ResultMap("userMap")
List<User> fingAll();

一对多查询

假设有一张 商品表 和 一张 图片表, 一个商品对应多张图片

那么如何取出一个商品,包含商品的所有属性,以及对应的所有图片呢?

实体类

商品的所有字段,同时,要添加一个 List<String> 表示多张图片的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Product {
private Integer id;
private Integer user_id;
private String name;
private String price;
private Date gmt_create;
private String description;
private Integer cate_id;
private Integer number;

// 关键!
private List<String> link;

// 省略 getter setter
}

图片的所有属性,用 String 表示图片地址

1
2
3
4
5
public class Image {
private Integer id;
private Integer product_id;
private String link;
}

Mapper接口

在图片的Mapper接口中,根据商品id找到对应的所有图片

1
2
3
// 根据商品id找图片
@Select("SELECT link from image WHERE product_id = #{product_id}")
List<String> getImageLinksByProductId(Integer product_id);

在商品的Mapper接口中,通过 @Results@Many 来进行关联

1
2
3
4
5
6
7
8
9
10
// 取出在售的所有商品,最新的排前面
@Select("select * from product WHERE number > 0 ORDER BY id DESC")
@Results({
// 这里要对id进行限定,否则 id 会为 null
@Result(property = "id", column = "id"),

// 将 image 的 link 和 product 的 id 绑定,通过 @Many 查询 返回 List
@Result(property = "link", column = "id", javaType = List.class, many = @Many(select = "com.zsc.tradeplatform.mapper.ImageMapper.getImageLinksByProductId")),
})
List<Product> getAll();

控制器

1
2
3
4
5
@ResponseBody
@GetMapping("/api/product")
public List<Product> getAllProduct(){
return productService.getAllProduct();
}

访问 127.0.0.1:8080/api/product 查看结果