搭建项目骨架

创建 Maven 项目,写好 pom.xml,引入依赖(Spring、SpringMVC、MyBatis、数据库驱动、JSTL 等)。

  • 建好基本目录结构:controller/service/mapper/pojo/resources/webapp/WEB-INF/

使用maven导入依赖

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
85
86
87
88
89
90
91
92
93
<!--Spring以及基础组件 + SpringMvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>

<!--jackson内容协商Converter 用于支持application/json的@RequestBody/@ResponseBody-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>

<!--Mybatis与Mybatis-Spring整合包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.15</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>

<!--Servlet-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!--JSP-->
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
<version>3.0.0</version>
<scope>provided</scope>
</dependency>

<!--数据库相关-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.25</version>
</dependency>

<!--logback日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.6</version>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
</dependency>

需要注意的是使用${spring.version}要在<properties>中配置<spring.version>6.0.6</spring.version>


配置底层支撑

数据库配置

src/main/resources/jdbc.properties,配置数据库连接信息。

1
2
3
4
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///test
jdbc.username=root
jdbc.password=123456

准备 MyBatis 配置文件 src/main/resources/mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--除了mybatis的标准日志实现,其他的方式都可以自动识别 不用显式配置-->
<!--<settings>
<setting name="logImpl" value="SLF4J"/>
</settings>-->

<settings>
<setting name="useGeneratedKeys" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>

<typeAliases>
<package name="com.wq3stone.pojo"/>
</typeAliases>
</configuration>

实体类 + Mapper 接口

实体类

pojo/ 下建实体类 Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.wq3stone.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @ClassName: Employee
* @Description:
* @Author: wq3stone
* @Create: 2025/9/13 19:25
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer empId;

private String empName;

private Double empSalary;
}

@Data注解

@Data是lombok提供的一个组合注解,它整合了多个基础注解的功能,包括

  1. @Getter:提供get方法
  2. @Setter:提供set方法
  3. @ToString:提供toString方法
  4. @EqualsAndHashCode:提供equals和hashCode方法
  5. @RequiredArgsConstructor:生成该类下被final修饰或者带有@NonNull的构造方法

@AllArgsConstructor注解

生成包含所有参数的构造函数

@NoArgsConstructor注解

生成无参构造函数

Mapper接口

mapper/ 下建接口EmployeeMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.wq3stone.mapper;
import com.wq3stone.pojo.Employee;
import java.util.List;

/**
* @InterFaceName: EmployeeMapper
* @description:
* @author: wq3stone
* @create: 2025/9/13 20:59
**/
public interface EmployeeMapper {
List<Employee> selectList();
}

resources/mapper/ 下写对应的 XML EmployeeMapper.xml(SQL 语句)。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wq3stone.mapper.EmployeeMapper">

<select id="selectList" resultType="com.wq3stone.pojo.Employee">
SELECT * FROM t_emp
</select>
</mapper>

MybatisConfig

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
package com.wq3stone.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
* @ClassName: MybatisConfig
* @Description:
* @Author: xuweiLi
* @Create: 2025/6/25 07:57
**/
@PropertySource("classpath:jdbc.properties") //配置文件读取
@MapperScan(basePackages = "com.wq3stone.mapper") //需要扫描生成代理的接口所在包
@EnableTransactionManagement //开启声明式事务
public class MybatisConfig {

@Autowired
private Environment env;

//druid数据源
@Bean
public DataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(env.getProperty("jdbc.driverClassName"));
ds.setUrl(env.getProperty("jdbc.url"));
ds.setUsername(env.getProperty("jdbc.username"));
ds.setPassword(env.getProperty("jdbc.password"));
return ds;
}

//sqlSession会话工厂
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//数据源
sqlSessionFactoryBean.setDataSource(dataSource());
//加载本地配置文件 当前类路径的开始 mapper文件夹下的mybatis配置文件 对于现在的纯注解开发,一般没啥存在的必要了
sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
//mapper.xml文件所在位置 所有类路径的开始 mapper文件夹下的 所有xml文件
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}

//Mapper扫描器 已被注解@MapperScan代替 具体参见: org.mybatis.spring.annotation.MapperScannerRegistrar

//数据源管理器,mybatis的事务交由spring来管理
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}

}

RootConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.wq3stone.config;

import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* @ClassName: SpringAppConfig
* @Description: 这是Spring父容器的主配置类,通常一般不需要做什么事情,基本的整合即可
* @Author: wq3stone
* @Create: 2025/9/13 23:56
**/
@Configuration
@EnableAspectJAutoProxy
@Import({MybatisConfig.class})
@ComponentScan(value = "com.wq3stone",
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {RestControllerAdvice.class})
})
public class RootConfig {

}

到这一步,可以用 JUnit 测试 Service/DAO 层,确保 MyBatis 和数据库能正常交互。


配置业务层 (Service)

service接口

service/ 下写接口(EmployeeService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.wq3stone.service;

import com.wq3stone.pojo.Employee;

import java.util.List;

/**
* @ClassName: EmployeeService
* @Description:
* @Author: wq3stone
* @Create: 2025/9/13 21:00
**/
public interface EmployeeService {
List<Employee> findList();
}

service实现类

service/impl/下写实现类(EmployeeServiceImpl

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
package com.wq3stone.service.impl;

import com.wq3stone.mapper.EmployeeMapper;
import com.wq3stone.pojo.Employee;
import com.wq3stone.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

/**
* @ClassName: EmployeeServiceImpl
* @Description:
* @Author: wq3stone
* @Create: 2025/9/13 21:01
**/
@Service
@Transactional
public class EmployeeServiceImpl implements EmployeeService {

@Autowired
private EmployeeMapper employeeMapper;

@Override
public List<Employee> findList() {
return employeeMapper.selectList();
}
}

@Service注解

标记的类专注于业务逻辑,通过组件扫描,Spring 自动检测使用 @Service 注解的类,并注册为 Spring 容器的 Bean。其作用相当于在applicationContext.xml写了

1
2
3
<bean id="EmployeeServiceImpl"
class="com.wq3stone.servicve.impl.EmployeeServiceImpl" scope="prototype">
</bean>

@Autowired注解

实现依赖注入,用于自动装配Spring Bean的依赖关系

@Transactional注解

@Transactional 标注在类上时,表示这个类 所有的 public 方法 默认都开启事务

此时可以写 JUnit 测试 Service 方法,确保业务逻辑能正常调用 Mapper。


配置 Web 层 (SpringMVC)

WebConfig

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
package com.wq3stone.config;

import com.wq3stone.interceptor.MyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
* @ClassName: WebConfig
* @Description:
* @Author: wq3stone
* @Create: 2025/9/13 22:20
**/
@Configuration
@EnableWebMvc //相当于以前的mvc:annotation-driven
@ComponentScan("com.wq3stone.controller,com.wq3stone.exceptionhandler")
public class WebConfig implements WebMvcConfigurer {

//JSP视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}

//启用静态资源处理
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

//首页Controller
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}

//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()); //自定义拦截器
}
}

Controller + 页面

EmployeeController

controller/ 下写控制器 EmployeeController

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
package com.wq3stone.controller;

import com.wq3stone.pojo.Employee;
import com.wq3stone.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @ClassName: EmployeeController
* @Description:
* @Author: wq3stone
* @Create: 2025/9/14 21:02
**/
@RestController
@RequestMapping("/employee")
public class EmployeeController {

@Autowired
private EmployeeService employeeService;

@GetMapping
public List<Employee> findList() {
return employeeService.findList();
}
}

views/ 下写 JSP(如 index.jsplist.jsp)。


补充功能

配置拦截器(interceptor/

MyInterceptor

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
package com.wq3stone.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
* @ClassName: MyInterceptor
* @Description:
* @Author: wq3stone
* @Create: 2025/9/14 08:28
**/
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("MyInterceptor.preHandle");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("MyInterceptor.postHandle");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("MyInterceptor.afterCompletion");
}
}

配置全局异常处理(exceptionhandler/

GlobalExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.wq3stone.exceptionhandler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* @ClassName: GlobalExceptionHandler
* @Description:
* @Author: wq3stone
* @Create: 2025/9/14 21:13
**/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String exceptionHandler(Exception e) {
log.error("发生内部错误",e);
return "出现了未知错误:" + e.getMessage();
}
}

Web 容器初始化(initialize/

WebApplicationInitializer

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
package com.wq3stone.initialize;

import com.wq3stone.config.RootConfig;
import com.wq3stone.config.WebConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
* @ClassName: WebApplicationInitializer
* @Description: Spring3.0+实现了Servlet3.0的初始化接口继承 AbstractAnnotationConfigDispatcherServletInitializer
* 只需要指定配置类的所在和映射地址, 具体的容器初始化逻辑交由父类去做,这样既省去了web.xml配置,又省去了spring的配置文件
* @Author: wq3stone
* @Create: 2025/9/14 21:19
**/
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

/**
* 指定Spring配置类
* @return Spring配置类的Class
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 指定SpringMvc配置类
* @return SpringMvc配置类的Class
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

/**
* DispatchServlet的映射路径
* @return 应去映射哪个地址
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}

优化事务(<tx:annotation-driven>)。