一、概述
1.1 业务场景
1,数据库的读写分类
2,SAAS服务多数据源
3,由于数据较大,要分库分表
1.2 主要的实现方式
(1)、使用分包方式,不同的数据源配置不同的MapperScan和mapper文件
(2)、使用AOP切片方式,实现动态数据源切换,AbstractRoutingDataSource
(3)、使用数据库代理中间件,如Mycat、shardingJDBC 等
1.3 不同方式之间的区别
- 分包方式可以集合JTA(JAVA Transactional API)实现分布式事务,但是整个流程的实现相对来说比较复杂。
- AOP动态配置数据源方式缺点在于无法实现全局分布式事务,所以如果只是对接第三方数据源,不涉及到需要保证分布式事务的话,是可以作为一种选择。
- 使用数据库代理中间件方式是现在比较流行的一种方式,很多大厂也是使用这种方式,开发者不需要关注太多与业务无关的问题,把它们都交给数据库代理中间件去处理,大量的通用的数据聚合,事务,数据源切换都由中间件来处理,中间件的性能与处理能力将直接决定应用的读写性能,比较常见的有Mycat、TDDL等。现在阿里出了100%自研的分布式数据库OceanBase,从最底层支持分布式,性能也非常强大,大家感兴趣的可以去了解下!
1.4 原理如下
一个spring和Mybatis的框架的项目中,我们在spring配置中往往是配置一个dataSource来连接数据库,然后绑定给sessionFactory,在dao层代码中再指定sessionFactory来进行数据库操作。
正如上图所示,每一块都是指定绑死的,如果是多个数据源,也只能是下图中那种方式。
可看出在Dao层代码中写死了两个SessionFactory,这样日后如果再多一个数据源,还要改代码添加一个SessionFactory,显然这并不符合开闭原则。
二、多种实现方式
2.1 【分包方式】实现简单的多数据源整合
主要是MyBatis框架分包方式实现
2.1.1 配置两个数据源配置
# 项目启动端口
server:
port: 9090
# 项目 名称
spring:
application:
name: multi-datasource-instance
datasource:
# 主数据库
master:
# 注意,整合多数据源时如果使用springboot默认的数据库连接池Hikari,指定连接数据使用的是jdbc-url而不是url属性
jdbc-url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 副数据库
slave:
# 注意,整合多数据源时如果使用springboot默认的数据库连接池Hikari,指定连接数据使用的是jdbc-url而不是url属性
jdbc-url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
2.1.2 编写主副数据库数据源配置
主数据源相关配置:主要是指定主数据源、扫描的mapper地址、事务管理器等信息。
@Configuration
// 指定主数据库扫描对应的Mapper文件,生成代理对象
@MapperScan(basePackages ="com.diary.it.multi.datasource.mapper" ,sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
// mapper.xml所在地址
private static final String MAPPER_LOCATION = "classpath*:mapper/*.xml";
/**
* 主数据源,Primary注解必须增加,它表示该数据源为默认数据源
* 项目中还可能存在其他的数据源,如获取时不指定名称,则默认获取这个数据源,如果不添加,则启动时候回报错
*/
@Primary
@Bean(name = "masterDataSource")
// 读取spring.datasource.master前缀的配置文件映射成对应的配置对象
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource dataSource() {
DataSource build = DataSourceBuilder.create().build();
return build;
}
/**
* 事务管理器,Primary注解作用同上
*/
@Bean(name = "masterTransactionManager")
@Primary
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* session工厂,Primary注解作用同上
*/
@Bean(name = "masterSqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MasterDataSourceConfig.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
2.1.3 副数据源相关配置
主要是指定数据源、扫描的mapper地址、事务管理器等信息。
@Configuration
// 指定从数据库扫描对应的Mapper文件,生成代理对象
@MapperScan(basePackages = "com.diary.it.multi.datasource.mapper2", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
// mapper.xml所在地址
private static final String MAPPER_LOCATION = "classpath*:mapper2/*.xml";
/**
* 数据源
*/
@Bean(name = "slaveDataSource")
// 读取spring.datasource.slave前缀的配置文件映射成对应的配置对象
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource dataSource() {
DataSource build = DataSourceBuilder.create().build();
return build;
}
/**
* 事务管理器
*/
@Bean(name = "slaveTransactionManager")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* session工厂
*/
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SlaveDataSourceConfig.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
2.1.4 UserController层
/** *控制层 */
@RestController
public class UserController {
@Autowired
private UserService userService;
/**
* 通过用户名获取用户信息,以及从库的地址信息
*/
@RequestMapping(value = "/user",method = RequestMethod.GET)
public User findUserByName(@RequestParam(value = "name",required = true) String name){
return userService.findByName(name);
}
}
2.1.5 Service层
照常注入主从两个Dao层
@Service
public class UserService {
@Autowired
private UserDao userDao;
// 主数据源
@Autowired
private CityDao cityDao;
// 从数据源
public User findByName(String userName) {
User user = userDao.findByName(userName);
City city = cityDao.findByName("上海市");
System.out.println(city);
user.setDescription("从数据库->"+city.getDescription());
user.setCity(city);
return user;
}
}
2.2 AbstractRoutingDataSource AOP 方式实现
AbstrictRoutingDataSource的本质就是利用一个Map将数据源存储起来,然后通过Key来得到Value来修改数据源。
2.2.1 主要步骤
在 SpringBoot 项目中实现读写分离通常需要以下几步:
- 配置数据源:你需要为读操作和写操作分别配置一个数据源。
- 创建数据源路由逻辑:这通常通过扩展 Spring 的 AbstractRoutingDataSource 来实现。它允许你根据一定的逻辑来决定使用哪个数据源(读或写)。
- 配置事务管理器:这使得你能够在使用不同数据源时保持事务的一致性。
- 服务层或DAO层设计:确保在执行读操作时使用读数据源,在执行写操作时使用写数据源。
- 自定义切面,在切面中解析 @DataSource 注解。当一个方法或者类上面,有 @DataSource 注解的时候,将 @DataSource 注解所标记的数据源列出来存入到 ThreadLocal 中。
注意:这里使用ThreadLocal的原因是为了保证我们的线程安全。
2.2.2 配置文件类
spring:
datasource:
local:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
remote:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.85.111:3306/demo?serverTimezone=UTC&useUnicode=true@characterEncoding=utf-8
2.2.3 创建数据源枚举类
package com.mashibing.mult;
public enum DataSourceType {
REMOTE,
LOCAL
}
2.2.4 数据源切换处理
package com.mashibing.mult;
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源变量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
System.out.printf("切换到{%s}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源变量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
package com.mashibing.mult;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
//afterPropertiesSet 也可以重修此方法
super.afterPropertiesSet();
}
/**
* 根据Key获取数据源的信息
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
2.2.5 注入数据源
package com.mashibing.mult;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.remote")
public DataSource remoteDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.local")
public DataSource localDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource remoteDataSource, DataSource localDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.REMOTE.name(), remoteDataSource);
targetDataSources.put(DataSourceType.LOCAL.name(), localDataSource);
return new DynamicDataSource(remoteDataSource, targetDataSources);
}
}
2.2.6 自定义多数据源切换注解
package com.mashibing.mult;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 切换数据源名称
*/
DataSourceType value() default DataSourceType.REMOTE;
}
2.2.7 AOP拦截类的实现
package com.mashibing.mult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.mashibing.mult.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
2.2.8 使用切换数据源注解
package com.mashibing.mult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class EmpController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/local")
@DataSource(value = DataSourceType.LOCAL)
public List<Map<String, Object>> local(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from emp");
return maps;
}
@GetMapping("/remote")
@DataSource(value = DataSourceType.REMOTE)
public List<Map<String, Object>> remote(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from emp");
return maps;
}
}
2.3 Dynamic-datasource
MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
使用MP提供的Dynamic-datasource多数据源框架,实现在不同数据源间切换,通过@DS
注解实现对master和slave数据库的选择,展示了从配置、持久层到控制层的完整代码示例。
dynamic-datasource特性
- 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 支持数据库敏感配置信息 加密 ENC()。
- 支持每个数据库独立初始化表结构schema和数据库database。
- 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
- 支持 自定义注解 ,需继承DS(3.2.0+)。
- 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
- 提供 自定义数据源来源 方案(如全从数据库加载)。
- 提供项目启动后 动态增加移除数据源 方案。
- 提供Mybatis环境下的 纯读写分离 方案。
- 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
- 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 基于seata的分布式事务方案。
- 提供 本地多数据源事务方案。 附:不能和原生spring事务混用。
2.3.1 引入依赖
<!-- 多数据切换所需依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
2.3.2 配置文件
sever:
# 端口
port: 8080
# 配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
# 数据库路径jdbc:mysql://localhost:3306/mydb 的缩写,并配置时区
url: jdbc:mysql:///mydb?serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave:
url: jdbc:mysql:///mydb2?serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 打印MyBatis SQL 日志
logging:
level:
com.guqueyue.test.dao: debug # 写接口的包名
注意:
这里数据源的名字可以自定义,什么master、slave都可以随意命名。
这里可以根据格式自行添加数据源的个数,并且支持多种数据库,如oracle、达梦等。此处为了演示方便,都使用了mysql数据库。
2.3.3 dao层
package com.guqueyue.test.dao;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.guqueyue.test.entity.User;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* @Author: guqueyue
* @Description: 映射接口UserMapper
* @Date: 2023/12/19
**/
public interface UserMapper extends BaseMapper<User> {
@DS("master") // 默认为主数据源,其实可以省略
@Select("select * from users")
List<User> selectUserList();
@DS("slave") // 切换为slave数据源
@Select("select * from users")
List<User> selectUserListBySlave();
}
注意:
@DS
注解不止可以用在持久层,可以用在任意的类和方法上。@DS
注解作用在方法上的优先级 > 类。
也就是说可以在类上加一个
@DS
注解默认一个该类的数据源,如
@DS("master")
,
再在具体的方法上加一个
@DS
注解做个性化指定,如
@DS("slave")
,效果等同:
/**
* @Author: guqueyue
* @Description: 映射接口UserMapper
* @Date: 2023/12/19
**/
@DS("master") // 默认为主数据源,其实可以省略
public interface UserMapper extends BaseMapper<User> {
@Select("select * from users")
List<User> selectUserList();
@DS("slave") // 切换为slave数据源
@Select("select * from users")
List<User> selectUserListBySlave();
}
2.3.4 创建service
package com.guqueyue.test.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.guqueyue.test.entity.User;
import java.util.List;
/**
* @Author: guqueyue
* @Description: 用户service接口
* @Date: 2023/12/19
**/
public interface IUserService extends IService<User> {
List<User> selectUserList(String type);
}
创建service实现类,并调用持久层接口,根据传入的参数切换不同的方法:
package com.guqueyue.test.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guqueyue.test.dao.UserMapper;
import com.guqueyue.test.entity.User;
import com.guqueyue.test.service.IUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* @Author: guqueyue
* @Description: 用户实现类
* @Date: 2023/12/19
**/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private UserMapper userMapper;
@Override
public List<User> selectUserList(String type) {
// do something: 此处可根据实际情况进行业务选择,如根据当前登录的用户信息来选择不同的数据库
// 此处为了方便演示,直接用前端传入的type来判断
return "slave".equals(type) ? userMapper.selectUserListBySlave()
: userMapper.selectUserList();
}
}
2.3.5 .编写控制层代码
package com.guqueyue.test.controller;
import com.guqueyue.test.entity.User;
import com.guqueyue.test.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Author: guqueyue
* @Description: 用户控制层
* @Date: 2023/12/19
**/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* 查询用户列表
* @return
*/
@RequestMapping("/list")
public List<User> userList(String type) {
System.out.println("接收到的数据源类型为:" + type);
return userService.selectUserList(type);
}
}
2.4 JdbcTemplate
jdbcTemplate连接数据库就是用jdbcTemplate对象去调用它的query、udate、insert等方法操作数据库。
jdbcTemplate必须有dao层实现类,因为jdbcTemplate是在dao层用jdbcTemplate对应的方法操作sql语句的
2.4.1 配置文件
在
application.properties
或
application.yml
中配置两个数据源的基本属性:
# 数据源1配置
spring.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.datasource1.username=user1
spring.datasource1.password=pass1
spring.datasource1.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源2配置
spring.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.datasource2.username=user2
spring.datasource2.password=pass2
spring.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver
创建配置类来定义两个数据源的Bean和对应的
JdbcTemplate
:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource1")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource2")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
2.4.2 定义数据库表对应的实体类
package com.qf.entity;
import java.util.Date;
public class Users {
private Integer userId;
private String username;
private Date birthday;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Users{" +
"userId=" + userId +
", username='" + username + '\'' +
", birthday=" + birthday +
", age=" + age +
'}';
}
}
2.4.3 dao层
package com.qf.dao;
import com.qf.entity.Users;
import java.util.List;
//mybatis框架操作数据库有两种方式,一种是配置文件方式,一种是注解方式
public interface UsersDao {
public List<Users> getall();
}
package com.qf.dao.impl;
import com.qf.dao.UsersDao;
import com.qf.entity.Users;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository//Repository给当前类创建对象
public class UsersDaoImpl implements UsersDao {
@Resource(name = "onetemplate")
private JdbcTemplate jdbcTemplate;
//@Autowired
//@Qualifier("db2JdbcTemplate")
//private JdbcTemplate jdbcTemplate;
@Override
public List<Users> getall() {
List<Users> userList = jdbcTemplate.query("SELECT * FROM users", new RowMapper<Users>() {
@Override
public Users mapRow(ResultSet resultSet, int i) throws SQLException {
Users users = new Users();
users.setAge(resultSet.getInt("age"));//第二个age是实体类Users的age属性对应的表的字段名。反正第二个age是字段名,第一个age是实体类属性
users.setBirthday(resultSet.getDate("birthday"));
users.setUsername(resultSet.getString("username"));
users.setUserId(resultSet.getInt("userId"));
return users;
}
});
return userList;
}
}
2.4.4 service层
package com.qf.service;
import com.qf.entity.Users;
import java.util.List;
public interface UsersService {
public List<Users> getall();
}
package com.qf.service.impl;
import com.qf.dao.UsersDao;
import com.qf.entity.Users;
import com.qf.service.UsersService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UsersServiceImpl implements UsersService{
@Resource
private UsersDao usersDao;
@Override
public List<Users> getall() {
return usersDao.getall();
}
}
2.4.5 控制曾代码
package com.qf.controller;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.qf.entity.Users;
import com.qf.service.UsersService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.Resource;
import java.util.List;
@Controller
public class TestController {
@Resource
private UsersService usersService;
@RequestMapping("/test")//访问此请求的地址是localhost:8080/test
public String test( ModelMap map){
System.out.println("testjsp-----------");
List<Users> usersList = usersService.getall();
System.out.println("userList:"+usersList);
map.addAttribute("usersList",usersList);
return "show";//此时实际跳去的页面时/show.jsp.
}
}
版权归原作者 常生果 所有, 如有侵权,请联系我们删除。