使用Spring Boot和MyBatis创建一个Hello World的应用程序

怎么了? le?)

我们来尝试在Spring Boot环境下做一个类似于MyBatis的Hello World的事情。

环境

這次的環境。

$ java --version
openjdk 11.0.15 2022-04-19
OpenJDK Runtime Environment (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 11.0.15, vendor: Private Build, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-109-generic", arch: "amd64", family: "unix"

我决定使用PostgreSQL 14.2作为数据库,并在Docker中准备。

$ docker container run \
  -it --rm --name postgres \
  -p 5432:5432 \
  -e POSTGRES_DB=example \
  -e POSTGRES_USER=charon \
  -e POSTGRES_PASSWORD=password \
  postgres:14.2-bullseye

创建一个Spring Boot+MyBatis项目

使用Spring Initializr创建一个可以使用MyBatis的Spring Boot项目。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.6.7 \
  -d javaVersion=11 \
  -d name=spring-hello-mybatis \
  -d groupId=com.example \
  -d artifactId=hello-mybatis \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=com.example.spring.mybatis \
  -d dependencies=mybatis,postgresql \
  -d baseDir=hello-mybatis | tar zxvf -

进入目录内。

$ cd hello-mybatis

不需要使用自动生成的源代码,因此将其删除。

$ rm src/main/java/com/example/spring/mybatis/SpringHelloMybatisApplication.java src/test/java/com/example/spring/mybatis/SpringHelloMybatisApplicationTests.java

我也会查看依存关系和插件设置等。

	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</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>

使用mvn dependency:tree命令查看,发现其中包括了spring-boot-starter和spring-boot-starter-jdbc,所以只需使用mybatis-spring-boot-starter就足够了。

[INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.2.2:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.6.7:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.6.7:compile
[INFO] |  |  |  \- org.springframework:spring-context:jar:5.3.19:compile
[INFO] |  |  |     +- org.springframework:spring-aop:jar:5.3.19:compile
[INFO] |  |  |     \- org.springframework:spring-expression:jar:5.3.19:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.7:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.7:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.29:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.6.7:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:4.0.3:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.3.19:compile
[INFO] |  |     +- org.springframework:spring-beans:jar:5.3.19:compile
[INFO] |  |     \- org.springframework:spring-tx:jar:5.3.19:compile
[INFO] |  +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.2.2:compile
[INFO] |  +- org.mybatis:mybatis:jar:3.5.9:compile
[INFO] |  \- org.mybatis:mybatis-spring:jar:2.0.7:compile

编写源代码

首先,让我们确定一个桌子。这次我们选择这个作为主题。

drop table if exists person;

create table person (
  id serial,
  first_name varchar(20),
  last_name varchar(20),
  age integer,
  primary key(id)
);

接下来,我们将写Java源代码。

关于MyBatis,我们将以这个作为参考。

 

映射到数据库表格的模型。

package com.example.spring.mybatis;

public class Person {
    Long id;
    String firstName;
    String lastName;
    Integer age;

    public static Person create(String firstName, String lastName, Integer age) {
        Person person = new Person();

        person.setFirstName(firstName);
        person.setLastName(lastName);
        person.setAge(age);

        return person;
    }

    // getter、setterは省略
}

接下来,创建一个Mapper接口。

package com.example.spring.mybatis;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface PersonMapper {
    @Insert("insert into person(first_name, last_name, age) values(#{firstName}, #{lastName}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Person person);

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName}")
    List<Person> findByLastName(String lastName);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAge(Person person);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAgePrimitive(String lastName, int age);
}

本次,我们将使用注解而不是XML文件来创建Mapper接口。

对于像BlogMapper这样的映射器类,还有一种更巧妙的方法。它们的映射语句根本不需要通过XML进行映射,而是可以使用Java注解。

 

如果给予@Mapper注解,MyBatis Spring Boot Starter会自动检测。

@Mapper
public interface PersonMapper {

MyBatis-Spring-Boot-Starter默认会搜索用@Mapper注解标记的映射器。

 

插入文本。

    @Insert("insert into person(first_name, last_name, age) values(#{firstName}, #{lastName}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Person person);

要将参数嵌入SQL中,可以使用#{变量名}来表示。

另外,由於這次使用了PostgreSQL的序列類型(serial),因此是自動編號。

要使用此选项,需要将useGeneratedKeys设置为true,然后使用keyProperty属性将编号结果映射到模型中。

映射器的 XML 文件/插入、更新和删除

在这个情况下,我们将使用@Options作为注解。

除此之外,还有一些select语句的变种。

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName}")
    List<Person> findByLastName(String lastName);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAge(Person person);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAgePrimitive(String lastName, int age);

最後一个示例是为了确认多个参数,但是如果有多个参数,您需要通过@Param显式指定名称,或在编译时指定-parameters选项并将useActualParamName设置为true(默认值)。

从3.4.3版本开始,通过指定每个参数的名称,可以以任意顺序编写arg元素。要通过参数名称引用构造函数参数,您可以给它们添加@Param注解,也可以使用’-parameters’编译器选项编译项目并启用useActualParamName(默认情况下已启用此选项)。

映射器XML文件/结果映射

此次项目是使用Spring Initializr创建的,因此spring-boot-starter-parent成为了父pom,并且已经指定了-parameters。所以我们将在这个前提下继续进行。

 

创建一个使用这个Mapper接口的类。

package com.example.spring.mybatis;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class Runner implements ApplicationRunner {
    Logger logger = LoggerFactory.getLogger(Runner.class);
    PersonMapper personMapper;

    public Runner(PersonMapper personMapper) {
        this.personMapper = personMapper;
    }

    @Transactional
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // insert
        Person maruko = Person.create("ももこ", "さくら", 9);
        Person sakiko = Person.create("さきこ", "さくら", 11);
        Person tamae = Person.create("たまえ", "穂波", 9);

        personMapper.insert(maruko);
        personMapper.insert(sakiko);
        personMapper.insert(tamae);

        List
                .of(maruko, sakiko, tamae)
                .forEach(p -> logger.info("inserted: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select all
        List<Person> allPeople = personMapper.findAll();
        allPeople.forEach(p -> logger.info("find all: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by last_name
        List<Person> sakuraFamily = personMapper.findByLastName("さくら");
        sakuraFamily.forEach(p -> logger.info("find by last_name: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by object
        List<Person> results = personMapper.findByLastNameAndAge(Person.create(null, "さくら", 10));
        results.forEach(p -> logger.info("find by object: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by primitives
        List<Person> results2 = personMapper.findByLastNameAndAgePrimitive("さくら", 10);
        results2.forEach(p -> logger.info("find by primitives: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));
    }
}

插入数据后,我分别执行了准备好的SELECT语句。

最后是主要的类,即 main class。

package com.example.spring.mybatis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class);
    }
}

进行设置

我们也来进行一下设置吧。

使用spring.datasource.*属性来设置与准备好的PostgreSQL的连接信息。

spring.datasource.url=jdbc:postgresql://localhost:5432/example
spring.datasource.username=charon
spring.datasource.password=password

spring.sql.init.mode=always

mybatis.configuration.map-underscore-to-camel-case=true

将spring.sql.init.mode设置为always是为了始终执行schema.sql。

我把mybatis.configuration.map-underscore-to-camel-case设置为true的原因是,

这是为了将在 Mapper 接口中创建的查询结果的列名映射到 Java 的驼峰命名规则属性。

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

在这种写法的情况下,如果不将mybatis.configuration.map-underscore-to-camel-case设置为true,获取查询结果时不会设置带有下划线的列的值。

映射器XML文件/自动映射

 

除此之外,还可以设置resultMap来解决问题,但是这次我们将按照这个策略进行。

进行

那么,我将尝试执行一下。

$ mvn spring-boot:run

日志的结果。

2022-04-30 21:14:34.597  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.598  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.598  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 3, first_name = たまえ, last_name = 穂波, age = 9
2022-04-30 21:14:34.605  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.606  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.606  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 3, first_name = たまえ, last_name = 穂波, age = 9
2022-04-30 21:14:34.608  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by last_name: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.608  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by last_name: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.610  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by object: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.612  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by primitives: id = 2, first_name = さきこ, last_name = さくら, age = 11

在执行INSERT语句后,通过序列(serial)生成的ID已被应用于模型中。

inserted: id = 1, first_name = ももこ, last_name = さくら, age = 9
inserted: id = 2, first_name = さきこ, last_name = さくら, age = 11
inserted: id = 3, first_name = たまえ, last_name = 穂波, age = 9

选择文也可以的嘛。

find all: id = 1, first_name = ももこ, last_name = さくら, age = 9
find all: id = 2, first_name = さきこ, last_name = さくら, age = 11
find all: id = 3, first_name = たまえ, last_name = 穂波, age = 9


find by last_name: id = 1, first_name = ももこ, last_name = さくら, age = 9
find by last_name: id = 2, first_name = さきこ, last_name = さくら, age = 11


find by object: id = 2, first_name = さきこ, last_name = さくら, age = 11

find by primitives: id = 2, first_name = さきこ, last_name = さくら, age = 11

这次应该就是这样了吧。

bannerAds