Integrating MyBatis with SSM: Core Concepts and Configuration
MyBatis is a lightweight persistence framework that supports custom SQL, stored procedures, and advanced mappings. It eliminates most of the boilerplate JDBC code, such as parameter setting and result set handling. With minimal XML configuration or annotations, MyBatis maps plain Java objects (POJOs) to database records, enabling seamless interaction between Java applications and relational databases.
Comparison of Persistence Frameworks
-
JDBC: SQL embedded directly in Java code leads to tight coupling, making maintenance difficult and increasing the risk of hard-coded errors. Frequent SQL changes require constant code updates, reducing development efficiency.
-
Hibernate / JPA: Offer high-level abstraction and rapid development but struggle with complex queries. Generated SQL is often inflexible for optimization. Full-object mapping becomes cumbersome when partial data retrieval is needed. Excessive reflection impacts performance.
-
MyBatis: Strikes a balance—lightweight, performant, and flexible. Separates SQL logic from Java code, allowing developers to focus on business logic while keeping SQL clean and manageable. While slightly slower than Hibernate in development speed, it offers full control over SQL execution.
Development Efficiency: Hibernate > MyBatis > JDBC
Runtime Performance: JDBC > MyBatis > Hibernate
Key Design Principle
In MyBatis, SQL is no longer writtten inside Java classes. Instead, it's defined externally in XML files or via annnotations, improving readability and maintainability. The Mapper interface replaces traditional DAO patterns—only an interface is required, without a concrete implementation. Actual SQL is placed in corresponding XML mapping files.
Example Setup
Employee Entity Class:
package com.ergou.pojo;
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
// Getters and setters
public Integer getEmpId() { return empId; }
public void setEmpId(Integer empId) { this.empId = empId; }
public String getEmpName() { return empName; }
public void setEmpName(String empName) { this.empName = empName; }
public Double getEmpSalary() { return empSalary; }
public void setEmpSalary(Double empSalary) { this.empSalary = empSalary; }
}
Mapper Interface:
package com.ergou.mapper;
import com.ergou.pojo.Employee;
public interface EmployeeMapper {
Employee queryById(Integer id);
int deleteById(Integer id);
}
XML Mapping File (EmployeeMapper.xml):
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.ergou.mapper.EmployeeMapper">
<select id="queryById" resultType="com.ergou.pojo.Employee">
SELECT emp_id AS empId, emp_name AS empName, emp_salary AS empSalary
FROM t_emp
WHERE emp_id = #{empId}
</select>
<delete id="deleteById">
DELETE FROM t_emp WHERE emp_id = #{id}
</delete>
</mapper>
Main Configuration (mybatis-config.xml):
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example" />
<property name="username" value="liergou" />
<property name="password" value="liergou070509" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/EmployeeMapper.xml" />
</mappers>
</configuration>
Test Case:
@Test
public void testQuery() throws IOException {
try (InputStream configStream = Resources.getResourceAsStream("mybatis-config.xml")) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(configStream);
try (SqlSession session = factory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee employee = mapper.queryById(1);
System.out.println(employee);
session.commit();
}
}
}
Evolution from iBATIS
MyBatis evolved from iBATIS by introducing stronger type safety and cleaner abstractions. In iBATIS, you could directly use sqlSession.selectOne() without defining interfaces. However, this approach limited flexibility—methods like selectOne only accepted one parameter.
For example, in iBATIS:
<mapper namespace="StudentMapper">
<select id="query" resultType="com.ergou.pojo.Student">
SELECT * FROM student WHERE sid = #{id}
</select>
</mapper>
And the test:
Student student = sqlSession.selectOne("StudentMapper.query", 1);
This limitation was resolved in MyBatis through the use of interface-based proxies, enabling method signatures with multiple parameters.
Parameter Binding
#{} vs ${}
#{}acts as a placeholder, safely binding values via prepared statements. Ideal for all user inputs to prevent SQL injection.${}performs string substitution, useful for dynamic table names, column names, or keywords.
Best practice: Prefer #{} unless dynamic SQL requires ${}.
Example using @Param annotation:
@Select("SELECT * FROM user WHERE ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
Input Handling
Simple Types
When passing a single primitive or String, the placeholder name doesn’t matter—it’s treated as a single value.
<delete id="deleteById">
DELETE FROM t_emp WHERE emp_id = #{id}
</delete>
Single POJO
Use property names matching the object’s fields.
<insert id="insertEmployee">
INSERT INTO t_emp(emp_name, emp_salary)
VALUES(#{empName}, #{empSalary})
</insert>
Multiple Simple Parameters
Use @Param to assign meaningful keys:
List<Employee> queryByNameAndSalary(@Param("name") String name, @Param("salary") Double salary);
Or rely on default naming (arg0, param1, etc.) if no annotation is used.
Map Parameters
Access map keys directly in SQL:
<insert id="insertEmployeeMap">
INSERT INTO t_emp(emp_name, emp_salary)
VALUES(#{name}, #{salary})
</insert>
Output Handling
Primitive Return Types
For DML operations (INSERT, UPDATE, DELETE), return type is automatically inferred as int or long (number of affected rows).
<delete id="deleteById">
DELETE FROM t_emp WHERE emp_id = #{id}
</delete>
Query Results
Specify resultType for queries. Valid values include:
- Full class name
- Built-in aliases (e.g.,
string,integer,date,map,list)
Custom aliases can be registered:
<typeAliases>
<typeAlias alias="Employee" type="com.ergou.pojo.Employee" />
<package name="com.ergou.pojo" />
</typeAliases>
Auto-Camel Case Mapping
Enable automatic conversion from underscore to camel case:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
This allows emp_id to map to empId automatically.
ResultMap for Complex Mappings
When field-to-property mapping isn't automatic, define a resultMap:
<resultMap id="employeeResultMap" type="com.ergou.pojo.Employee">
<id column="emp_id" property="empId" />
<result column="emp_name" property="empName" />
<result column="emp_salary" property="empSalary" />
</resultMap>
<select id="queryById" resultMap="employeeResultMap">
SELECT emp_id, emp_name, emp_salary FROM t_emp WHERE emp_id = #{id}
</select>
Primary Key Handling
Auto-Icnremented Keys
Use useGeneratedKeys="true" and keyProperty to capture generated IDs:
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
INSERT INTO t_emp(emp_name, emp_salary)
VALUES(#{empName}, #{empSalary})
</insert>
Non-Auto-Increment Keys
Use <selectKey> to generate primary keys before insert:
<insert id="insertEmployee">
<selectKey keyProperty="empId" resultType="Integer" order="BEFORE">
SELECT NEXTVAL('emp_seq')
</selectKey>
INSERT INTO t_emp(emp_id, emp_name, emp_salary)
VALUES(#{empId}, #{empName}, #{empSalary})
</insert>
Column-Property Mismatch
When database column names differ from Java properties:
- Use aliases in SQL:
SELECT emp_id AS empId - Enable camel case mapping globally
- Define explicit
resultMapfor full control