Avery's Blog

Work on Web Dev, Infra Dev & ML

0%

SpringBoot+MyBatis 实战

ORM 与 MyBatis

Object Relational Mapping (ORM),对象关系映射,用于实现数据库系统和面向对象编程语言类型系统之间的数据转换。效果上,它创建了一个可在编程语言中使用的“虚拟对象数据库”。

MyBatis 是 ORM 的一种实现框架,对 JDBC 进行了又一层的封装。和 Hibernate 这种重量级的 ORM 框架相比,MyBatis 非常轻量,其源码可以在数天之间就全部看完。这是因为和 Hibernate 提供了全套映射机制、自动生成 SQL 语句相比,MyBatis 选择了拥抱 SQL,让程序员自己写 SQL,只是把查询结果转换到 POJO 中。

Maven

Apache Maven,是一个软件项目管理及自动构建工具,使用项目对象模型 (Project Object Model, POM) 进行配置。

代码分层

一般而言,一个 Web 项目,自顶向下的访问顺序为:Controller -> Service -> Dao -> MyBatis -> Database

Controller 用作 URL 映射;Service 写业务逻辑;Dao 即 Data Access Object,数据访问对象,用途正如其名;Model 定义实体类,即 MyBatis 从数据库中取出的数据对应的 POJO。

此外,Controller 和 Service 命名相对固定,Dao 常被命名为 Mapper 或者 Repository,Model 还常被命名为 Entity 或者 Domain。以下使用 Controller -> Service -> Dao -> Model 的命名。

SpringBoot+Mybatis 配置

SpringBoot 基础结构

src/main/java 程序开发以及主程序入口
src/main/resources 配置文件
src/test/java 测试程序

src/main/java 目录下:
model (entity, domain):实体层,存放实体类;与数据库中的属性值基本保持一致,实现 get 和 set 的方法。
service:服务层,主要是业务类代码。
controller:控制层,负责页面访问控制。
dao (mapper, repository):数据访问层,和数据库通信。

引入 Web 模块

pom.xml 中添加 web 模块,也可在 IDEA 中的 Spring Initializr 中勾选:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

pom.xml 中默认有两个模块:spring-boot-starterspring-boot-starter-test

spring-boot-starter 是核心模块,包括自动配置支持、日志和 YAML。如果引入了 spring-boot-starter-web 可以去掉此模块,因为 web 模块自动依赖此模块。

spring-boot-starter-test 是测试模块,包括 JUnit、Hamcrest 和 Mockito。

mybatis-spring-boot-starter 是 SpringBoot 集成的 MyBatis 模块,可以完全使用注解实现 CRUD,几乎完全不用写 XML 配置文件,轻松上手。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

第一个 Controller

1
2
3
4
5
6
7
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public String index() {
return "Hello World";
}
}

这时候访问 http://localhost:8080/hello 应该就可以看到 Hello World 了。

自定义 Property

  1. 配置 Maven 文件,如果是使用 Intellij IDEA 中的 Spring Initializr 创建项目的话,Maven 应该是自动配置好的。

  2. resources/application.properties 添加数据库连接的相关配置

    1
    2
    3
    spring.datasource.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8
    spring.datasource.username = root
    spring.datasource.password = root

    SpringBoot 会自动加载 spring.datasource.* 中的相关配置,数据源会自动注入到 sqlSessionFactory 中,sqlSessionFactory 会自动注入到 Mapper 中,做到开箱即用。

Spring Boot 常用注解

声明 Bean 的注解

@Component:没有明确角色的组件
@Service:在业务逻辑层(service 层)使用
@Repository:在数据访问层(dao 层)使用
@Controller:在控制层(controller 层)使用
@RestController:是一个组合注解,等于 @Controller + @ResponseBody

此外,
@Scope:作用在类和方法上,用于配置 Spring Bean 的作用域
@RequestMapping:用来处理请求地址映射
@ResponseBody:支持将返回值放在 Response 体内,而不是返回一个视图

注入 Bean 的注解

@Autowired:实现自动装配,Spring IoC 容器扫描到 @Autowired 注解会去查询实现类,并自动注入。
@Qualifier:如果 @Autowired 指定的是一个接口,它有多个实现类,那么需要 @Qualifier 注解指定注入的实现类的名称。

实现 Java 配置的注解

@Configuration 注解声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件。
@Bean 注解作用在方法上,声明当前方法的返回值是一个 Java Bean。

MyBatis 注解

可以使用接口+注解的方式取代 XML 配置 Mapper 的方式,以完成 Dao 层的编写。在项目小、数据库交互少的情况下,这是更简洁、更优雅的方式。但当项目增长到一定规模,有大量复杂的 CRUD 的情况下,还是使用 XML 配置文件管理 SQL 语句更有效率。

@Select 负责查询。
@Insert 负责插入。
@Update 负责修改。
@Delete 负责删除。
@Result 修饰返回的结果集,关联实体类属性和数据库字段。如果实体类属性和数据库字段保持一致,就不需要这个注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Mapper
public interface UserDao {

@Select("SELECT * FROM user WHERE name = #{name}")
User findByName(@Param("name") String name);

@Insert("INSERT INTO user(name,age) VALUES(#{name},#{age})")
int insert(@Param("name") String name, @Param("age") Integer age);

@Update("UPDATE user SET age=#{age} WHERE name=#{name}")
void update(User user);

@Delete("DELETE FROM user WHERE id =#{id}")
void delete(Long id);
}

实体类的编写

在实体类中定义属性,实现 get 和 set 方法,把数据库中的数据转换成 Java 程序能理解的实体类。

代码实现

以下以展示汽车销量的应用为例。

在 Controller 中注入依赖的 Service。

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class CarSaleController {
@Autowired
CarSaleService carSaleService;

@RequestMapping(value = {"/car/TotalSaleMonth"})
@ResponseBody
public CarTotalSaleMonth getCarTotalSaleEveryMonth() {
return carSaleService.getCarTotalSaleEveryMonth();
}
}

匹配的 Service:

1
2
3
4
5
6
7
8
9
@Service
public class CarSaleService {
@AutoWired
CarSaleDao carSaleDao;

public CarTotalSaleMonth getCarTotalSaleEveryMonth() {
return carSaleDao.selectCarTotalSaleEveryMonth();
}
}

匹配的 Dao:

1
2
3
4
5
6
@Mapper
public interface CarSaleDao {
// car_sale_month 是数据表名,其中存着 month1...month12 总共 12 个属性值
@Select({"select * from car_sale_month"})
CarTotalSaleMonth selectCarTotalSaleEveryMonth();
}

匹配的 Model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CarTotalSaleMonth {
int month1;
int month2;
// ...
int month12;

public int getMonth1() {
return month1;
}

public void setMonth1(int month1) {
this.month1 = month1;
}

// ...
}

Welcome to my other publishing channels