Mybatis is a popular orm framework. It can maintain SQL statements in XML and is very flexible. This is the key for many developers to choose it as an orm framework.

Using mybatis in spring boot applications is easy. Only a few steps are required.

This article focuses on “How to integrate mybatis in spring boot”. It does not explain the detailed usage of mybatis. If you have questions, you can refer to the official mybatis documentation.

Create a Maven project

springboot mybatis maven project

pom

The essential dependencies are: mysql driver, database connection pool, mybatis-spring-boot-starter.

It is easier to use the officially provided mybatis-spring-boot-starter.

 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<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>
 <groupId>io.springcloud</groupId>
 <artifactId>springboot-mybatis</artifactId>
 <version>0.0.1-SNAPSHOT</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.6.4</version>
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <encoding>UTF-8</encoding>
  <java.version>17</java.version>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.junit.vintage</groupId>
   <artifactId>junit-vintage-engine</artifactId>
   <scope>test</scope>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
    <exclusion>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-logging</artifactId>
    </exclusion>
   </exclusions>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-undertow</artifactId>
  </dependency>
  
  <!-- MYSQL driver is required -->
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
  </dependency>
  
  <!-- Using the high-performance HikariCP database connection pool -->
  <dependency>
   <groupId>com.zaxxer</groupId>
   <artifactId>HikariCP</artifactId>
  </dependency>
  
  <!-- MyBatis starter -->
  <dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>2.2.2</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
     <executable>true</executable>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

mapper Interface definition

Create a Mapper interface whose implementation will be dynamically generated during application runtime. Just as a demo, so it’s not too complicated. Only a simple query method now is provided. Gets the time on the database server.

1
2
3
4
5
6
7
8
package io.springcloud.mybatis.mapper;
import java.time.LocalDateTime;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface FooMapper {
    LocalDateTime now();
}

mapper xml files

I put the xml file in the src/main/resources/mapper directory.

1
2
3
4
5
6
7
8
9
<!-- foo-mapper.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="io.springcloud.mybatis.mapper.FooMapper">
    <select id="now" resultType="java.time.LocalDateTime">
        SELECT NOW();
    </select>
</mapper>

application.yaml

 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
server:
  port: 80
 

logging:
  # Set the log level of the package where the mapper interface is located to DEBUG, so that you can see more detailed SQL execution logs.
  level:
    "io.springcloud.mybatis.mapper": "DEBUG"

spring:
  # MYSQL data source configuration. No need to explain too much.
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
    username: root
    password: root

mybatis:
  # Set the xml configuration file address for mybatis.
  # config-location: "classpath:mybatis/mybatis-config.xml"

  # Loads all files in the 'mapper' directory (regardless of directory hierarchy) with names ending in '-mapper.xml'.
  mapper-locations:
    - "classpath:mapper/**/*-mapper.xml" 

mybatis.config-location is used to set the path to the xml configuration file for mybatis. The configuration file allows you to configure mybatis plugins, cache, etc. For demo projects, this configuration is not required. If you want to know more, you can refer to the official documentation.

mybatis.mapper-locations is a key configuration. It specifies the path to load the xml file for the mapper interface (wildcards can be used). If the configuration is incorrect and the xml file is not loaded correctly, then an exception will occur at runtime: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)...

If the xml file is not in the current project, for example in another jar package. Then you need to use classpath*: as path prefix.

Mybatis has more configuration items here, which you can view through the configuration class org.mybatis.spring.boot.autoconfigure.MybatisProperties or the official documentation.

annotation drivers

Set the package where the mapper interface resides (there can be more than one) via the @MapperScan annotation.

An annotation class can also be set via the annotationClass attribute. For example annotationClass = Mapper.class. Then only the interfaces annotated with @Mapper will be loaded.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package io.springcloud.mybatis;

import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = { "io.springcloud.mybatis.mapper" }, annotationClass = Mapper.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

test

After everything is ready to go. Verify that everything is working by running a test.

 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
package io.springcloud.test;

import java.time.format.DateTimeFormatter;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import io.springcloud.mybatis.Application;
import io.springcloud.mybatis.mapper.FooMapper;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationTest {

    static final Logger LOGGER = LoggerFactory.getLogger(ApplicationTest.class);

    @Autowired
    FooMapper fooMapper;

    @Test
    @Transactional(readOnly = true)
    @Rollback(false)
    public void test () {
        LOGGER.info("result={}", this.fooMapper.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }
}

The key logs output from the console are as follows:

1
2
3
4
5
6
7
8
2022-03-10 18:55:59.606  INFO 10860 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-03-10 18:56:00.300  INFO 10860 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-03-10 18:56:00.311  INFO 10860 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@457c9034 testClass = ApplicationTest, testInstance = io.springcloud.test.ApplicationTest@c7ba306, testMethod = test@ApplicationTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@345f69f3 testClass = ApplicationTest, locations = '{}', classes = '{class io.springcloud.mybatis.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@c730b35, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@22555ebf, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@10683d9d, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@662ac478, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3d285d7e, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@3c407114], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.jdbc.support.JdbcTransactionManager@118fbaf0]; rollback [false]
2022-03-10 18:56:01.061 DEBUG 10860 --- [           main] i.s.mybatis.mapper.FooMapper.now         : ==>  Preparing: SELECT NOW();
2022-03-10 18:56:01.100 DEBUG 10860 --- [           main] i.s.mybatis.mapper.FooMapper.now         : ==> Parameters: 
2022-03-10 18:56:01.141 DEBUG 10860 --- [           main] i.s.mybatis.mapper.FooMapper.now         : <==      Total: 1
2022-03-10 18:56:01.146  INFO 10860 --- [           main] io.springcloud.test.ApplicationTest      : result=2022-03-10 18:56:01
2022-03-10 18:56:01.161  INFO 10860 --- [           main] o.s.t.c.transaction.TransactionContext   : Committed transaction for test: [DefaultTestContext@457c9034 testClass = ApplicationTest, testInstance = io.springcloud.test.ApplicationTest@c7ba306, testMethod = test@ApplicationTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@345f69f3 testClass = ApplicationTest, locations = '{}', classes = '{class io.springcloud.mybatis.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@c730b35, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@22555ebf, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@10683d9d, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@662ac478, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3d285d7e, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@3c407114], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

Everything is OK.

Summary

As you can see, it’s quite simple. Let’s summarize the key steps.

  1. Add the required dependencies (database driver, data source, mybats starter).
  2. Set the mybatis.mapper-locations property in the application configuration file. This is used to set the load path of the xml file for the mapper interface.
  3. Set the package path of the mapper interface by annotating @MapperScan.