MyBatis with Spring Boot example

In this article we will explore how to create a spring boot application with mybatis. We will be using h2 embedded database, so you would not need to install any external database for this article.

We would be using an employee and department data model, I am pretty sure this data model has been beaten to death in blogs and everywhere, so I am assuming you are already familiar with the data model.

Most of the articles on this topic use annotations and auto configuration which hides most of complexity under the hood, in this article we would be doing it the “hard way” , we will be writing our own configurations and see what is under the hood.

Then we would optimise the code to use annotation and auto configure, if you are only looking for some code to copy and get it working, skip to the annotation section or head to github where you can get the code. If you want to understand whats under the hood, read on.

MyBatis with Spring Boot example

You take the blue pill – the story ends. You take the red pill – you stay in Wonderland and I show you how deep the rabbit hole goes.

Morpheus

What is MyBatis?

MyBatis is an open source persistence framework that simplifies database interaction for your application. Unlike ORM (Object Relational Mapping) like Hibernate, MyBatis does not handle relations and you would need to write most of the queries by hand. Though it seems daunting at first, writing queries give you much more finer control on how your queries execute.

If you have an existing or older data model, which does not relent nicely to hibernate or ORM style of data model, Mybatis is a great tool for managing your data access layer.

Maven dependency

Let us add the dependency using maven, initially we would be working with only basic dependency and in later part, we would be adding auto configure dependencies.

<?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 http://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.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.learnithardway.mybatis</groupId>
    <artifactId>mybatis-springboot-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Why use xml config?

Now you may wonder why am I using xml when every one is using annotation. XML are really verbose and define every thing explicitly, which is why I prefer explaining using XML rather than annotations. Annotations hide the explicit definitions of configuration, beans under the hood, only when the things do not work the way they are supposed to, then we try to peep under the hood to see why it is not working.

It is like popping the hood of the car, you do not regularly check oil in your car or check pressure in tyres, you do it only when things go wrong. If you were trained using open hood and explained where the engine is and where the oil is under the hood, in case things go wrong you would still be able to handle the situation.

Do I use xml in my day job? of course not, but I still use xml when I am trying to define bespoke configurations, like using a different schema for test or based on profiles e.t.c.

Configuration

For MyBatis to work, we would need two configuration file, one is the mybatis-config.xml which contains the overall definition of our datasource, other is the mapper xml which would contain the queries.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.h2.Driver"/>
                <property name="url" value="jdbc:h2:~/employee"/>
                <property name="username" value="sa"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="EmployeeMapper.xml"/>
    </mappers>
</configuration>
Mapper.xml

In our spring boot example, we would be initialising the mybatis manually using a config bean.

package com.learnithardway.mybatis.config;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;

@Configuration
public class MyBatisConfig {

	@Bean
	public SqlSessionFactory getSessionFactory() {
		String resource = "mybatis-config.xml";
		InputStream inputStream = null;
		try {
			inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactory sqlSessionFactory =
					new SqlSessionFactoryBuilder().build(inputStream);
			return sqlSessionFactory;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}

Query Mapping

In the mapper xml, we would define our queries, which should be DML i.e. Insert, updates and Selects or you can call your stored procedures as well via 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="com.learnithardway.mybatis.repo.EmployeeMapper">

    <select id="selectEmployee" resultType="com.learnithardway.mybatis.model.Employee">
        select id,name from Employee where id = #{id}
    </select>

    <insert id="createEmployee" parameterType="com.learnithardway.mybatis.model.Employee">
        insert into Employee (id,name) values ( #{id}, #{name} )
    </insert>

</mapper>

This is a fairly simple example where we are using insert and select and it maps directly to the object and returns the object. Let us look at the repository class for Employee.

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Employee;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository {

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	public Employee getEmployee(Integer empId) {
		try (SqlSession session = sqlSessionFactory.openSession()) {
						Employee emp = session.selectOne("com.learnithardway.mybatis.repo.EmployeeMapper.selectEmployee",empId);
			return emp;
		}
	}

	@Override
	public Integer createEmployee(Employee employee) {
		try (SqlSession session = sqlSessionFactory.openSession()) {
			int insertkey = session.insert("com.learnithardway.mybatis.repo.EmployeeMapper.createEmployee",employee);
			return insert;
		}
	}

}

Notice we are using same name which we used in namespace in mapper xml, the convention you can use is namespace.queryid, the namespace does not need to in the format of package name, it could be any string which you like, however if we define the name space as a classname, we can use the namespace in a way which would make our repository code a bit cleaner, this would require us to define an interface called EmployeeMapper and use fully qualified name of this interface as namespace in mapper. Please note that the name of method should match the id in xml query.

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Employee;

public interface EmployeeMapper {

	public Employee selectEmployee(Integer id);

	public Integer createEmployee(Employee employee);
}

Now we can use this mapper in our repository class.

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Employee;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository {

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	public Employee getEmployee(Integer empId) {
		try (SqlSession session = sqlSessionFactory.openSession()) {
			Employee emp = session.getMapper(EmployeeMapper.class).selectEmployee(empId);
			return emp;
		}
	}

	@Override
	public Integer createEmployee(Employee employee) {
		try (SqlSession session = sqlSessionFactory.openSession()) {
			int insertkey = session.getMapper(EmployeeMapper.class).createEmployee(employee);
			return insertkey;
		}
	}

}

This just makes the code bit cleaner, in later portion we would see how our mapper can remove the need of repository at all.

Now lets try and test the code to see if it works, we would be using a spring boot test to make sure that the application works.

package com.learnithardway.mybatis;

import com.learnithardway.mybatis.model.Department;
import com.learnithardway.mybatis.model.Employee;
import com.learnithardway.mybatis.repo.DepartmentRepository;
import com.learnithardway.mybatis.repo.EmployeeRepository;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;

@SpringBootTest
class MybatisExampleApplicationTest {

	@Autowired
	EmployeeRepository employeeRepository;

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	@Autowired
	DepartmentRepository departmentRepository;

	@BeforeEach
	public void setupDatabase() {
		try {
			Connection connection = sqlSessionFactory.openSession().getConnection();
			Statement statement = connection.createStatement();
			URL resource = ClassLoader.getSystemClassLoader().getResource("setup.sql");
			List lines = Files.readAllLines(Path.of(resource.toURI()));
			for (String line : lines) {
				if (!line.trim().isEmpty()) {
					statement.execute(line);
				}
			}
			connection.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testGetEmployee() {
		Employee employee = employeeRepository.getEmployee(101);
		Assertions.assertEquals("ABCD", employee.getName(), "Employee name should match");
	}

	@Test
	public void testCreatemployee() {
		Employee employee1 = new Employee();
		employee1.setName("test");
		Integer employee = employeeRepository.createEmployee(employee1);
		Assertions.assertEquals(1, employee, "Employee id should be 1");
	}

}

We are using @beforeEach method to initialise our embedded database before each test, this is a very primitive form of db testing, in real life we would be using something more sophisticated like DBUnit or FlywayDB to initialise and maintain our database. Lets look at setup.sql

drop table employee if exists;

drop table department if exists;

create table employee ( id int auto_increment primary key, name varchar(200), dept_id int);

create table department ( id int auto_increment primary key, name varchar(200));

insert into employee (id,name,dept_id) values (101,'ABCD',1);

insert into employee (id,name,dept_id) values (102,'DEFG',1);

insert into employee (id,name,dept_id) values (103,'FGHI',1);

insert into employee (id,name,dept_id) values (104,'LMNO',1);

insert into department (id,name) values (1,'Alphabets');

N + 1 Loading Problem

Now lets look at some complex example where MyBatis shines, in any one to many relations, we alway have a dilemma to either load the parent and child together using a join or independently. So if we have N child and 1 parent, it creates a N + 1 queries, there is a nice stackoverflow thread if you want to know more about this problem.

In our case Department and Employee relation is a N + 1 relation. Lets look at how to map this in MyBatis, first lets map the join query in DepartmentMapper.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="DepartmentMapper">

    <select id="selectDepartment" resultMap="departmentMap">
        select d.id as dept_id,d.name as dept_name,e.name as e_name, e.id as e_id
        from Department d join Employee e on
        e.dept_id = d.id where d.id = #{id}
    </select>

    <resultMap id="departmentMap" type="com.learnithardway.mybatis.model.Department">
        <result property="id" column="dept_id"/>
        <result property="name" column="dept_name"/>
        <collection property="employeeList" javaType="ArrayList" column="dept_id"
                    ofType="com.learnithardway.mybatis.model.Employee">
            <result property="id" column="e_id"/>
            <result property="name" column="e_name"/>
        </collection>
    </resultMap>


</mapper>

In this case we have use resultMap instead of resultType, resultType is used when you have a simple type which can be populated using getters and setters. If you have a complex relation like this, then its better to use resultMap. We are mapping child using collection tag which means MyBatis is going to fire one query and remove duplicate department from result set and give us 1 department and 4 employees. Lets add a repository an quickly test it out.

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Department;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class DepartmentRepository {

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	public Department getDepartment(Integer department){
		try (SqlSession session = sqlSessionFactory.openSession()) {
			Department dep = session.selectOne(
					"DepartmentMapper.selectDepartment", department);
			return dep;
		}
	}
}

Now we would add a test method in our applicationtest

package com.learnithardway.mybatis;

import com.learnithardway.mybatis.model.Department;
import com.learnithardway.mybatis.model.Employee;
import com.learnithardway.mybatis.repo.DepartmentRepository;
import com.learnithardway.mybatis.repo.EmployeeRepository;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;

@SpringBootTest
class MybatisExampleApplicationTest {

	@Autowired
	EmployeeRepository employeeRepository;

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	@Autowired
	DepartmentRepository departmentRepository;

	@BeforeEach
	public void setupDatabase() {
		try {
			Connection connection = sqlSessionFactory.openSession().getConnection();
			Statement statement = connection.createStatement();
			URL resource = ClassLoader.getSystemClassLoader().getResource("setup.sql");
			List lines = Files.readAllLines(Path.of(resource.toURI()));
			for (String line : lines) {
				if (!line.trim().isEmpty()) {
					statement.execute(line);
				}
			}
			connection.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testGetEmployee() {
		Employee employee = employeeRepository.getEmployee(101);
		Assertions.assertEquals("ABCD", employee.getName(), "Employee name should match");
	}

	@Test
	public void testCreatemployee() {
		Employee employee1 = new Employee();
		employee1.setName("test");
		Integer employee = employeeRepository.createEmployee(employee1);
		Assertions.assertEquals(1, employee, "Employee id should be 1");
	}

	@Test
	public void testNPlusOneQueryRelation() {
		Department department = departmentRepository.getDepartment(1);
		Assertions.assertEquals(4, department.getEmployeeList().size());
	}
}

There are many more complex mapping which you can do in MyBatis xml, it gives you a very powerful medium to express your queries which would suit virtually any needs for database operations. Some of these features are supported in annotation while the more complex ones are not supported in annotation. For example the collection mapping, we did for department is not possible to do it using annotations. Joins are not supported in annotation.

Dynamic Queries in MyBatis

Let us look at one more example where MyBatis has very strong capabilities which is dynamic query. Dynamic query is any query where you create the query conditionally, for example a complex query for search where you form the query based on how many parameters have been provided.

 <select id="findEmployee" resultType="com.learnithardway.mybatis.model.Employee">
        select id,name from Employee where
        <if test="name != null">
            name like #{name}
        </if>
        <if test="id != null">
            id = #{id}
        </if>
    </select>

Using Annotations with Spring

Now its time to throw in the annotation juice to simplify the code, using annotations would remove the need for boilerplate code from our codebase. Most of these annotations are part of mybatis-spring integration so we would need to add new dependency in the pom for mybatis-spring.

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>

We are also using spring-jdbc to allow using some of the extra features it brings in.

@Mapper annotation

MyBatis-spring allows us to remove the boilerplate code in repository, the repositories in our case were not doing any thing meaningful. We will create a dynamic mapper using @Mapper annotation on our mapper interface. Lets decorate our mappers

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Employee;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface EmployeeMapper {

	public Employee selectEmployee(Integer id);

	public Integer createEmployee(Employee employee);

	public Employee findEmployee(Employee employee);
}

Now we need to initialise the mappers, so head over the config bean and initialise the mappers there.

package com.learnithardway.mybatis.config;

import com.learnithardway.mybatis.repo.DepartmentMapper;
import com.learnithardway.mybatis.repo.EmployeeMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.io.InputStream;

@Configuration
public class MyBatisConfig {

	@Bean
	public SqlSessionFactory getSessionFactory() {
		String resource = "mybatis-config.xml";
		InputStream inputStream = null;
		try {
			inputStream = Resources.getResourceAsStream(resource);
			SqlSessionFactory sqlSessionFactory =
					new SqlSessionFactoryBuilder().build(inputStream);
			return sqlSessionFactory;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Bean
	public EmployeeMapper employeeMapper() throws Exception {
		SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(getSessionFactory());
		return sqlSessionTemplate.getMapper(EmployeeMapper.class);
	}

	@Bean
	public DepartmentMapper departmentMapper() throws Exception {
		SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(getSessionFactory());
		return sqlSessionTemplate.getMapper(DepartmentMapper.class);
	}

}

Now we can safely delete the repository code and lets test it using our test class.

package com.learnithardway.mybatis;

import com.learnithardway.mybatis.model.Department;
import com.learnithardway.mybatis.model.Employee;
import com.learnithardway.mybatis.repo.DepartmentMapper;
import com.learnithardway.mybatis.repo.EmployeeMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.Statement;
import java.util.List;

@SpringBootTest
class MybatisExampleApplicationTest {

	@Autowired
	EmployeeMapper employeeMapper;

	@Autowired
	SqlSessionFactory sqlSessionFactory;

	@Autowired
	DepartmentMapper departmentMapper;

	@BeforeEach
	public void setupDatabase() {
		try {
			Connection connection = sqlSessionFactory.openSession().getConnection();
			Statement statement = connection.createStatement();
			URL resource = ClassLoader.getSystemClassLoader().getResource("setup.sql");
			List lines = Files.readAllLines(Path.of(resource.toURI()));
			for (String line : lines) {
				if (!line.trim().isEmpty()) {
					statement.execute(line);
				}
			}
			connection.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testGetEmployee() {
		Employee employee = employeeMapper.selectEmployee(101);
		Assertions.assertEquals("ABCD", employee.getName(), "Employee name should match");
	}

	@Test
	public void testCreatemployee() {
		Employee employee1 = new Employee();
		employee1.setName("test");
		Integer employee = employeeMapper.createEmployee(employee1);
		Assertions.assertEquals(1, employee, "Employee id should be 1");
	}

	@Test
	public void testNPlusOneQueryRelation() {
		Department department = departmentMapper.selectDepartment(1);
		Assertions.assertEquals(4, department.getEmployeeList().size());
	}
}

Using @Insert/@Update Annotations in MyBatis

Now that looks pretty clean but can we do something more. For simple queries MyBatis has @Select, @Insert and @Update annotations which can be directly used in Mapper class, which would allow you to skip the xml.

Lets convert our mapper to use @Select annotations instead of xml.

package com.learnithardway.mybatis.repo;

import com.learnithardway.mybatis.model.Employee;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface EmployeeMapper {

	@Select("select id,name from Employee where id = #{id}")
	public Employee selectEmployee(Integer id);

	@Insert(" insert into Employee (id,name) values ( #{id}, #{name} )")
	public Integer createEmployee(Employee employee);

	public Employee findEmployee(Employee employee);
}

We have used annotations in two of our three queries, if you remember findEmployee is a conditional query, for more complex cases, it would be my recommendation to use xml.

Spring Mybatis Auto configure

Most of the spring boot magic comes from auto configure jars, these jars have spring.factories file in META-INF which contains beans which should be instantiated by spring without explicitly being defined by your application. You can read more about auto configuration on spring doc. MyBatis has a project mybatis-spring-boot-autoconfigure which would allow you to get started with MyBatis in spring boot without going through any of the above process. Lets add the dependency and pop the hood.

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

this jar contains the spring.factories which is defined as

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

This would tell spring to instantiate these two classes on startup. Lets look at project-repo or you can use the link MybatisLanguageDriverAutoConfiguration and MybatisAutoConfiguration, if we look at the code you would find a sqlsessionfactory being defined, so we can skip the step of defining our own SQLSessionFactory if we have the datasource. For the datasource, also we can use data source auto-configuration which comes bundled with spring boot. So we need to define our datasource properties in application.properties of application.yml and spring would do the rest. Lets look at how we can modify our code to use autoconfigure.

We need to add only datasource properties in application.properties

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:~/employee
spring.datasource.username=sa
spring.datasource.password=

Now at this point, we dont see any mybatis related config, actually if you are not using xml then you dont need one. However in our case we are using dynamic query and complex mapping hence we would need to add an additional property telling where our mapper xmls are located.

mybatis.mapper-locations=classpath*:*Mapper.xml

Now we can get rid of mybatis-config.xml. Now we have exactly the necessary amount of code which is required. Checkout the finished code at my github repo.

Coding Diary. Github, Git and Repositories | by Jasmine Liu | Medium

Since I learnt it the hard way, I am showing you how you to learn it the hard way, and possibly thats the only real way to learn it forever.

Author

Nitiz

There are currently no comments.

Bitnami