Spring Annotation-Only Integration with MyBatis and JUnit
This tutorial demonstrates how to integrate Spring with MyBatis and JUnit using only annotations, covering Maven dependencies, Druid data source configuration, MyBatis SqlSessionFactory setup, mapper scanning, dynamic SQL with @SelectProvider, service layer wiring, and JUnit test execution with Spring's test context framework.
Project Setup and Dependencies
The article begins by creating a Maven project and declaring all required dependencies in pom.xml. The versions used are Spring 5.3.17, MyBatis 3.5.9, MyBatis-Spring 2.0.7, MySQL Connector/J 8.0.28, Druid 1.2.8, JUnit 4.13.2, and Apache Commons Lang3 3.9. The dependencies are grouped into Spring core ( spring-context, spring-jdbc, spring-test), database connectivity ( mysql-connector-java, druid), MyBatis integration ( mybatis, mybatis-spring), and testing utilities ( junit, commons-lang3). After saving the POM, a Maven refresh downloads the JARs.
Project Structure
The resulting project layout is organized by concern: config – Spring configuration classes dao – MyBatis mapper interfaces and dynamic SQL providers domain – Entity classes (JavaBeans) service – Business logic implementations EmployeeApp – Main entry point for manual verification resources – Property files (e.g., jdbc.properties) test – JUnit test classes
Database Preparation
A MySQL database named testdb and a table employee are created with columns e_id (PK), e_name, and e_salary. Seven rows of sample data are inserted. The jdbc.properties file in resources holds the connection details:
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
mysql.username=root
mysql.password=123456Spring Configuration Classes
JdbcConfig – DataSource with Druid
Annotated with @PropertySource("classpath:jdbc.properties") to load the properties file. Four @Value fields inject each property. A @Bean method returns a DruidDataSource configured with the injected values. Because there is only one data source, no explicit bean ID is needed.
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${mysql.driver}") private String driver;
@Value("${mysql.url}") private String url;
@Value("${mysql.username}") private String userName;
@Value("${mysql.password}") private String password;
@Bean
public DataSource getDataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}MybatisConfig – SqlSessionFactory and Mapper Scanning
This class defines two @Bean methods: getSqlSessionFactoryBean – Accepts the DataSource via @Autowired. Creates a SqlSessionFactoryBean, sets typeAliasesPackage to com.jobs.domain so that entity classes can be referenced by simple names, assigns the data source, and configures a Configuration with StdOutImpl logging to print SQL statements to the console. getMapperScannerConfigurer – Instantiates MapperScannerConfigurer and sets basePackage to com.jobs.dao, enabling automatic proxy generation for all mapper interfaces in that package.
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.jobs.domain");
ssfb.setDataSource(dataSource);
Configuration mybatisConfig = new Configuration();
mybatisConfig.setLogImpl(StdOutImpl.class);
ssfb.setConfiguration(mybatisConfig);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.jobs.dao");
return msc;
}
}SpringConfig – Composition via @Import
The main Spring configuration class uses @Configuration, @ComponentScan("com.jobs"), and @Import({JdbcConfig.class, MybatisConfig.class}). The author emphasizes that @Import is preferred over placing configuration methods directly in this class to avoid issues where configuration information cannot be retrieved.
@Configuration
@ComponentScan("com.jobs")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig {}MyBatis Mapping with Annotations
Entity Class – Deliberate Field Mismatch
The Employee class uses field names id, name, salary which differ from the database columns e_id, e_name, e_salary. This mismatch is intentional to demonstrate how @Results maps columns to properties.
public class Employee {
private Integer id;
private String name;
private Integer salary;
// constructors, getters/setters, toString()
}Mapper Interface – EmployeeDao
All CRUD operations are defined as interface methods with MyBatis annotations: add – @Insert with
@ResultMap("employee_map") update– @Update with
@ResultMap("employee_map") selectAll– @Select with inline @Results(id="employee_map", ...) defining three @Result mappings ( e_id → id, e_name → name, e_salary → salary) selectByCondition – Uses
@SelectProvider(type=EmployeeSQL.class, method="getSelectByConditionSQL")for dynamic SQL. Parameters are annotated with @Param ( ename, salaryStart, salaryEnd) while the method parameters have different names ( aaa, bbb, ccc) to show that @Param establishes the binding. deleteByIds – Uses
@DeleteProvider(type=EmployeeSQL.class, method="getDeleteByIdsSQL")with varargs Integer... empids.
public interface EmployeeDao {
@ResultMap("employee_map")
@Insert("insert into employee(e_id,e_name,e_salary) values(#{id},#{name},#{salary})")
Integer add(Employee emp);
@ResultMap("employee_map")
@Update("update employee set e_name=#{name},e_salary=#{salary} where e_id=#{id}")
Integer update(Employee emp);
@Results(id = "employee_map", value = {
@Result(column = "e_id", property = "id"),
@Result(column = "e_name", property = "name"),
@Result(column = "e_salary", property = "salary")})
@Select("select e_id,e_name,e_salary from employee order by e_id")
List<Employee> selectAll();
@ResultMap("employee_map")
@SelectProvider(type = EmployeeSQL.class, method = "getSelectByConditionSQL")
List<Employee> selectByCondition(@Param("ename") String aaa,
@Param("salaryStart") Integer bbb,
@Param("salaryEnd") Integer ccc);
@DeleteProvider(type = EmployeeSQL.class, method = "getDeleteByIdsSQL")
Integer deleteByIds(Integer... empids);
}Dynamic SQL Provider – EmployeeSQL
Two methods build SQL strings programmatically: getSelectByConditionSQL – Uses StringBuilder and StringUtils.isNotBlank to conditionally append LIKE and range predicates. The commented-out block shows an unsafe string-concatenation approach that is vulnerable to SQL injection; the active code uses parameter placeholders ( #{ename}, #{salaryStart}, #{salaryEnd}) to prevent injection. getDeleteByIdsSQL – Joins the varargs array into a comma-separated list for an IN clause. If the array is empty, it returns delete from employee where 1=2 to delete nothing safely.
public class EmployeeSQL {
public String getSelectByConditionSQL(@Param("ename") String xxx,
@Param("salaryStart") Integer yyy,
@Param("salaryEnd") Integer zzz) {
StringBuilder sql = new StringBuilder();
sql.append(" select e_id,e_name,e_salary from employee where 1=1");
if (StringUtils.isNotBlank(xxx)) {
sql.append(" and (e_name like CONCAT('%',#{ename},'%'))");
}
if (yyy != null) {
sql.append(" and e_salary >= #{salaryStart}");
}
if (zzz != null) {
sql.append(" and e_salary <= #{salaryEnd}");
}
sql.append(" order by e_id");
return sql.toString();
}
public String getDeleteByIdsSQL(Integer... ppp) {
StringBuilder sql = new StringBuilder();
sql.append("delete from employee");
if (ppp != null && ppp.length > 0) {
String idsql = StringUtils.join(ppp, ",");
sql.append(" where e_id in (").append(idsql).append(")");
} else {
sql.append(" where 1=2");
}
return sql.toString();
}
}Service Layer
EmployeeServiceis annotated with @Service("employeeService") (singleton by default). It injects the EmployeeDao proxy via @Autowired and delegates each call to the corresponding DAO method.
@Service("employeeService")
public class EmployeeService {
@Autowired private EmployeeDao employeeDao;
public Integer add(Employee emp) { return employeeDao.add(emp); }
public Integer update(Employee emp) { return employeeDao.update(emp); }
public List<Employee> selectAll() { return employeeDao.selectAll(); }
public List<Employee> selectByCondition(String name, Integer start, Integer end) {
return employeeDao.selectByCondition(name, start, end);
}
public Integer deleteByIds(Integer... eids) { return employeeDao.deleteByIds(eids); }
}Manual Verification – EmployeeApp
The main class bootstraps the Spring container using AnnotationConfigApplicationContext(SpringConfig.class), retrieves the employeeService bean, calls selectAll(), and prints each employee. The console output shows the executed SQL and the seven employee records.
public class EmployeeApp {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
EmployeeService empService = (EmployeeService) ctx.getBean("employeeService");
List<Employee> emplist = empService.selectAll();
for (Employee emp : emplist) {
System.out.println(emp);
}
}
}JUnit Integration
Spring integrates with JUnit 4 in two steps:
Add junit and spring-test dependencies with <scope>test</scope>.
Annotate the test class with @RunWith(SpringJUnit4ClassRunner.class) and @ContextConfiguration(classes = SpringConfig.class).
This enables @Autowired injection directly in the test class.
Test Class – EmployeeTest
Five test methods cover all DAO operations: add() – Inserts three new employees (IDs 100, 101, 102) and prints the affected row counts. update() – Updates employee 100 and prints the row count. selectAll() – Retrieves and prints all employees. selectByCondition() – Queries with name containing "任" and salary between 30000 and 60000. deleteByIds() – Deletes the three newly added employees (100, 101, 102).
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class EmployeeTest {
@Autowired private EmployeeService employeeService;
@Test public void add() { ... }
@Test public void update() { ... }
@Test public void selectAll() { ... }
@Test public void selectByCondition() { ... }
@Test public void deleteByIds() { ... }
}The article concludes that the annotation-only approach eliminates XML configuration, simplifies development, and the demo deliberately introduces real-world complexities (mismatched names, dynamic SQL, parameter binding) to illustrate practical solutions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
