前言
前面的文章中,我们创建了第一个基于thymeleaf模板引擎的网页,但是网页内并没有太多数据。本文,我们将介绍如何通过mybatis从mysql数据库中读取数据,包括简单的select
、update
、delete
、insert
,也有复杂的各种多表关联查询(一对一、一对多关联等)。如果你还没有安装好mysql,可以参考我们mysql安装的相关博文。
Maven引入相关jar包
引入Mybatis
第一步当然要通过maven的pom文件引入mybatis,代码如下:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
mybatis只是一个SQL层面的持久层框架,作用只是根据你的代码来生成SQL执行、返回结果。为了完成完整的数据库访问,我们还需要引入mysql-connector-java
等完成数据库连接的新建、管理工作。
引入mysql-connector-java
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
引入pagehelper
查询数据库的时候,难免有分页查询的需求,这时候pagehelper
非常好用。
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
引入lombok
数据库操作的时候,难免会有很多POJO类,然后类里的属性自然就会有很多get、set方法、构造方法、toString方法等等,重复劳作很累。所以引入lombok
可以有效缓解这种情况。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
完整pom文件代码
<?xml version="1.0" encoding="UTF-8"?>
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.coderbbb</groupId>
<artifactId>book-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>book-1</name>
<description>book-1</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置mybatis和数据库连接信息
配置mybatis
接下来我们需要简单配置一下mybatis,在springboot的resources文件夹下,有application.properties配置文件,在里面添加如下Mybatis配置项:
mybatis.configuration.map-underscore-to-camel-case=true
启用mybatis的驼峰法变量名映射。举个例子,springboot中我们有一个user类,里面有名为userAge
的属性。然后在数据库中,我们存储的对应字段名为user_age
。启用这个配置后,当我们查询数据,mybatis能够自动把user_age
和userAge
对应起来。mybatis.type-aliases-package=com.tec666.coderbbb.database.dos
配置springboot中你存储数据实体POJO类的包路径。比如我们的springboot项目中,有user这个POJO类,用来定义用户的各种数据属性。类似的各种POJO统一放在一个包里面,把包路径配置给mybatis。主要作用就是,当mybatis查询出数据后,需要从这个路径下面找到对应POJO类,把mysql数据转为对应POJO类返回给我们。mybatis.mapper-locations=classpath:mapper/*.xml
配置mybatis的xml sql文件存放地址。通常情况下,简单的SQL我们一般以注解的形式写在mapper接口中。但是,一些比较复杂的查询SQL,比如join、in等查询,尤其是需要SQL动态拼接的查询,我们需要写在xml文件中。
配置数据库连接信息
数据库连接配置,也是配置在application.properties中。主要是配置数据库连接地址、数据库用户名、密码等。代码如下:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
顾名思义,就是配置driver-class,这个通常一直是这个配置项。只有当你改变mysql版本、多种数据源等场景时,才需要关注这个值。spring.datasource.url=jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
数据库连接地址,请重点关注连接地址?
之后的内容。serverTimezone
是时区,当你数据库中保存时间戳等时间时,有时候会发现数据库中存的时间和查询出来的不一样,通常都是时区问题造成的。autoReconnect
这个是自动重连,建议配置上。useSSL
代表是否使用SSL来加密连接,看你的需求。通常使用SSL加密会对性能有一些影响。spring.datasource.username=zhang_san
数据库用户名spring.datasource.password=你的密码
数据库密码
完整application.properties文件代码
server.port=8080
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=你的密码
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.coderbbb.book1.database.dos
mybatis.mapper-locations=classpath:mapper/*.xml
创建POJO对象和Mysql数据表
为了演示查询,我们一共新建三个POJO对象:UserDO
、CarDO
、WifeDO
。我们规定:一个user可以有多辆car,但是只能有一个wife。所以,user和car是一对多的关系,user和wife是一对一的关系。主键的话,user和wife使用bigint
自增,car使用UUID生成36位字符串(为了演示,实际工作中,表的主键选择需要多种因素综合考量)。
我们在mybatis.type-aliases-package=com.coderbbb.book1.database.dos
这个配置项配置的路径下,新建这个三个POJO类,代码如下:
UserDO
代码
package com.coderbbb.book1.database.dos;
import lombok.Data;
@Data
public class UserDO implements Serializable {
/**
* 主键ID
*/
private Long id;
/**
* 用户名
*/
private String username;
}
CarDO
代码
package com.coderbbb.book1.database.dos;
import lombok.Data;
import java.io.Serializable;
@Data
public class CarDO implements Serializable {
/**
* 车辆表主键
*/
private String uuid;
/**
* 车辆所属用户
*/
private Long user;
/**
* 车辆名称:宝马、奔驰、奥迪、雅迪~
*/
private String name;
}
WifeDO
代码
package com.coderbbb.book1.database.dos;
import lombok.Data;
import java.io.Serializable;
@Data
public class WifeDO implements Serializable {
/**
* 主键ID
*/
private Long id;
/**
* 所属user的ID,简称:她老公
*/
private Long user;
/**
* 用户名
*/
private String username;
}
建表语句
这个没啥难度,代码如下:
UserDO
对应的user
表:
CREATE TABLE if not exists `user`
(
`id` bigint NOT NULL auto_increment,
`username` varchar(20) not null default '',
PRIMARY KEY (`id`),
KEY `idx_username` (`username`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='user表';
CarDO
对应的car
表:
CREATE TABLE if not exists `car`
(
`uuid` char(36) NOT NULL,
`user` bigint not null,
`name` varchar(20) not null default '',
PRIMARY KEY (`uuid`),
KEY `idx_name` (`name`),
KEY `idx_user` (`user`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='car表';
WifeDO
对应的wife
表:
CREATE TABLE if not exists `wife`
(
`id` bigint NOT NULL auto_increment,
`user` bigint not null,
`username` varchar(20) not null default '',
PRIMARY KEY (`id`),
KEY `idx_username` (`username`),
KEY `idx_user` (`user`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='wife表';
创建Mybatis Mapper和XML构建查询
创建Mapper,使用注解形式查询
通常简单的查询,我们可以直接使用注解的方式,快速构建SQL。首先我们新建三个Mapper的interface类:UserMapper
、CarMapper
、WifeMapper
,代码如下:
package com.coderbbb.book1.database.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper {
}
上面是UserMapper,其他类似。接下来我们在UserMapper里添加一个SQL插入语句,来给Mysql数据库插入一行记录。完整代码如下:
package com.coderbbb.book1.database.mapper;
import com.coderbbb.book1.database.dos.UserDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper {
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into user (username) values(#{d.username})")
int add(@Param("d") UserDO userDO);
}
上面的代码中,@Options(useGeneratedKeys = true, keyProperty = "id")
的意思是使用自增的long类型主键,名称是id
。三张表里,User
、Wife
使用的都是自增主键,Car
使用的是UUID字符串主键。所以car
的插入语句不需要配置这个。但是要在构造CarDO
的时候,给uuid
属性赋值(可以参考文章末尾,初始化数据的代码)。@Insert
代表括号中的是insert插入语句。其他还有@Select
、@Delete
、@Update
等,分别对应Mysql的select
,delete
,update
语句。
接下来,我们只要在springboot的controller或者service中,注入这个Mapper,然后调用对应的方法,即可完成对应的SQL操作。比如我们要插入一个User用户,使用如下代码即可。这里有一个常见问题,就是插入后,我们如何在当前方法获取到该记录在数据库中的主键?下方我们使用UserDO
插入后,调用UserDO
的getId()
方法,你会发现Mybatis已经自动把自增的ID值填入其中了。
package com.coderbbb.book1;
import com.coderbbb.book1.database.dos.UserDO;
import com.coderbbb.book1.database.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MybatisController {
@Autowired
private UserMapper userMapper;
@RequestMapping(value = "/mybatis/add", method = RequestMethod.GET)
public String add() {
UserDO userDO = new UserDO();
userDO.setUsername("张三");
if (userMapper.add(userDO) == 1) {
return "插入成功";
}
return "插入失败";
}
}
Mybatis通过XML完成一对一、一对多关联查询
注解形式的SQL查询,适合比较简单的SQL。如果比较复杂的场景,比如一对一、一对多等,使用mybatis的xml查询方式更为方便。
首先,我们构建一个名为UserDTO
的POJO类,里面包含了这个user的car和wife。UserDTO
这个类继承了UserDO
,在UserDO
的基础上增加了和它相关的car、wife等数据。代码如下:
package com.coderbbb.book1.database.dto;
import com.coderbbb.book1.database.dos.CarDO;
import com.coderbbb.book1.database.dos.UserDO;
import com.coderbbb.book1.database.dos.WifeDO;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class UserDTO extends UserDO implements Serializable {
/**
* 一个User有多辆车
*/
private List<CarDO> cars;
/**
* 一个用户有一个wife
*/
private WifeDO wifeDO;
}
然后,我们在前文application.properties中配置的mybatis xml路径中,新建名为UserMapper.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.coderbbb.book1.database.mapper.UserMapper">
</mapper>
其中,namespace
中的路径,是对应到UserMapper.java
这个文件的。相当于在UserMapper.java
中定义SQL操作的方法,然后在对应的xml中写具体的SQL代码。
我们在UserMapper.java
中写一个select方法,代码如下:
package com.coderbbb.book1.database.mapper;
import com.coderbbb.book1.database.dos.UserDO;
import com.coderbbb.book1.database.dto.UserDTO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into user (username) values(#{d.username})")
int add(@Param("d") UserDO userDO);
List<UserDTO> selectAll();
}
上面的代码中,List<UserDTO> selectAll();
这个方法,并没有使用@Select
注解写SQL,我们要把SQL写到对应的XML中。
- 首先,因为
UserDTO
这个POJO类无法直接和数据库中的表直接对应,所以我们要在xml文件中填写一下POJO类的字段和数据表中的字段,是如何对应的。 - 然后,我们要在xml中写好本次多张表联合查询的SQL。
完整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.coderbbb.book1.database.mapper.UserMapper">
<resultMap type="com.coderbbb.book1.database.dto.UserDTO" id="baseResultMap">
<result property="id" column="id" />
<result property="username" column="username" />
<!--一对一-->
<association property="wifeDO" javaType="com.coderbbb.book1.database.dos.WifeDO">
<result column="wife_id" property="id"/>
<result column="wife_username" property="username"/>
</association>
<!--一对多-->
<collection property="cars" ofType="com.coderbbb.book1.database.dos.CarDO" column="user">
<result column="car_uuid" property="uuid"/>
<result column="car_name" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="baseResultMap">
select u.*,w.id as wife_id,w.username as wife_username,c.uuid as car_uuid,c.name as car_name
from user u,wife w,car c
where u.id=w.user and u.id = c.user
</select>
</mapper>
然后我们在controller中调用Mapper中的selectAll
方法,来试试能否查询成功。代码如下:
/**
* 查询方法
* @return
*/
@RequestMapping(value = "/mybatis/select", method = RequestMethod.GET)
public List<UserDTO> select() {
List<UserDTO> data = userMapper.selectAll();
return data;
}
请求该controller的select方法后,返回的结果JSON如下:
[
{
"id": 1,
"username": "张三",
"cars": [
{
"uuid": "007df3c8-6fd3-4543-a689-450da2b19c3b",
"user": null,
"name": "小奔驰"
},
{
"uuid": "2482485a-a69b-4ce1-a016-4692a1ddd93b",
"user": null,
"name": "大宝马"
}
],
"wifeDO": {
"id": 1,
"user": null,
"username": "豆腐西施"
}
}
]
和我们预期的结构是完全一致的,查询成功!
附上Controller的完整代码,主要是有插入初始数据的代码,直接复制,省得你自己写。
package com.coderbbb.book1;
import com.coderbbb.book1.database.dos.CarDO;
import com.coderbbb.book1.database.dos.UserDO;
import com.coderbbb.book1.database.dos.WifeDO;
import com.coderbbb.book1.database.dto.UserDTO;
import com.coderbbb.book1.database.mapper.CarMapper;
import com.coderbbb.book1.database.mapper.UserMapper;
import com.coderbbb.book1.database.mapper.WifeMapper;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.UUID;
@RestController
public class MybatisController {
@Autowired
private UserMapper userMapper;
@Autowired
private WifeMapper wifeMapper;
@Autowired
private CarMapper carMapper;
@RequestMapping(value = "/mybatis/add", method = RequestMethod.GET)
public String add() {
UserDO userDO = new UserDO();
userDO.setUsername("张三");
if (userMapper.add(userDO) != 1) {
return "插入user失败";
}
CarDO carDO = new CarDO();
carDO.setName("大宝马");
carDO.setUser(userDO.getId());
carDO.setUuid(UUID.randomUUID().toString());
if(carMapper.add(carDO)!=1){
return "插入car失败";
}
carDO = new CarDO();
carDO.setName("小奔驰");
carDO.setUser(userDO.getId());
carDO.setUuid(UUID.randomUUID().toString());
if(carMapper.add(carDO)!=1){
return "插入car失败";
}
WifeDO wifeDO = new WifeDO();
wifeDO.setUser(userDO.getId());
wifeDO.setUsername("豆腐西施");
if(wifeMapper.add(wifeDO)!=1){
return "插入wife失败";
}
return "插入成功";
}
/**
* 查询方法
* @return
*/
@RequestMapping(value = "/mybatis/select", method = RequestMethod.GET)
public List<UserDTO> select() {
List<UserDTO> data = userMapper.selectAll();
return data;
}
}