Spring Boot MyBatis 数据库集群访问实现

本示例主要介绍了Spring Boot程序方式实现数据库集群访问,读库轮询方式实现负载均衡。阅读本示例前,建议你有AOP编程基础、mybatis基本功能会使用、数据库集群基本概念,这样你可以更快的理解和实现它

本示例源码

MySql主从配置

关于配置请参考《MySQL主从复制配置

Spring Boot实现方式

读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,一般来讲,主要有两种实现方式,分别为:

  • 1.使用中间件,比如Atlas,cobar,TDDL,mycat,heisenberg,Oceanus,vitess,OneProxy等
  • 2.使用程序自己实现,利用Spring Boot提供的路由数据源以及AOP,实现起来简单快捷(本文要介绍的方法)

程序代码实现

1.首先我们配置下pom.xml,添加示例必要的依赖

    <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>

2.数据源路由类功能RoutingDataSource.java

基于特定的key路由到特定的数据源。它内部维护了一组目标数据源,并且做了路由key与目标数据源之间的映射,提供基于key查找数据源的方法。

a.类关系图

b.代码很简单,调用下DBContext的get方法就可以了

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContext.get();
}
}

3.数据源上下文类DBContext.java

import com.easy.mybatis.multidatasource.enums.DBTypeEnum;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicInteger; @Slf4j
public class DBContext {
private static final ThreadLocal<DBTypeEnum> dbContext = new ThreadLocal<>(); private static final AtomicInteger counter = new AtomicInteger(-1); public static void set(DBTypeEnum dbType) {
dbContext.set(dbType);
} public static DBTypeEnum get() {
return dbContext.get();
} public static void master() {
set(DBTypeEnum.MASTER);
log.info("切换到master库");
} public static void slave() {
// 读库负载均衡(轮询方式)
int index = counter.getAndIncrement() % 2;
log.info("slave库访问线程数==>{}", counter.get());
if (index == 0) {
set(DBTypeEnum.SLAVE1);
log.info("切换到slave1库");
} else {
set(DBTypeEnum.SLAVE2);
log.info("切换到slave2库");
}
}
}

4.数据库枚举类DBTypeEnum.java

public enum DBTypeEnum {
MASTER, SLAVE1, SLAVE2
}

这里我们配置三个库,分别是一个写库Master,2个读库slave1,slave2

5.数据库配置类DataSourceConfig.java

package com.easy.mybatis.multidatasource.config;

import com.easy.mybatis.multidatasource.enums.DBTypeEnum;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; @Configuration
public class DataSourceConfig { @Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.slave1")
public DataSource slave1DataSource() {
return DataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.slave2")
public DataSource slave2DataSource() {
return DataSourceBuilder.create().build();
} @Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slave1DataSource") DataSource slave1DataSource,
@Qualifier("slave2DataSource") DataSource slave2DataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setDefaultTargetDataSource(masterDataSource);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}

6.mybatis配置类DataSourceConfig.java

package com.easy.mybatis.multidatasource.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.annotation.Resource;
import javax.sql.DataSource; @EnableTransactionManagement
@Configuration
@MapperScan("com.easy.mybatis.multidatasource.mapper")
public class MyBatisConfig { @Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource; @Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
} @Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(myRoutingDataSource);
}
}

7.切面类DataSourceAop.java

package com.easy.mybatis.multidatasource.config;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component; @Aspect
@Component
public class DataSourceAop {
@Pointcut("@annotation(com.easy.mybatis.multidatasource.annotation.Master) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.insert*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.add*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.update*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.edit*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.delete*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.remove*(..))")
public void writePointcut() { } @Pointcut("!@annotation(com.easy.mybatis.multidatasource.annotation.Master) " +
"&& (execution(* com.easy.mybatis.multidatasource.service..*.select*(..)) " +
"|| execution(* com.easy.mybatis.multidatasource.service..*.get*(..)))")
public void readPointcut() { } @Before("writePointcut()")
public void write() {
DBContext.master();
} @Before("readPointcut()")
public void read() {
DBContext.slave();
}
}

8.注解类Master.java

package com.easy.mybatis.multidatasource.annotation;

/**
* 主库,可读写
*/
public @interface Master {
}

9.用户的xml,mapper,service类

UserMapper.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.easy.mybatis.multidatasource.mapper.UserMapper">
<select id="selectById" resultType="com.easy.mybatis.multidatasource.entity.User" parameterType="int">
SELECT * from user WHERE id = #{id}
</select>
<select id="selectList" resultType="com.easy.mybatis.multidatasource.entity.User">
SELECT * from user
</select>
<insert id="insert" parameterType="com.easy.mybatis.multidatasource.entity.User">
INSERT into user(id,name,age,email) VALUES(#{id}, #{name},#{age},#{email})
</insert>
<update id="updateById" parameterType="com.easy.mybatis.multidatasource.entity.User">
UPDATE user SET name =#{name}, age =#{age},email =#{email} WHERE id =#{id}
</update>
<delete id="deleteById" parameterType="int">
DELETE FROM user WHERE id =#{id}
</delete>
</mapper>

UserMapper.java

package com.easy.mybatis.multidatasource.mapper;

import com.easy.mybatis.multidatasource.entity.User;
import org.springframework.stereotype.Repository; import java.io.Serializable;
import java.util.List; @Repository
public interface UserMapper {
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(User entity); /**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id); /**
* 根据 ID 修改
*
* @param entity 实体对象
*/
int updateById(User entity); /**
* 根据 ID 查询
*
* @param id 主键ID
*/
User selectById(Serializable id); List<User> selectList();
}

UserServiceImpl.java

package com.easy.mybatis.multidatasource.service.impl;

import com.easy.mybatis.multidatasource.annotation.Master;
import com.easy.mybatis.multidatasource.entity.User;
import com.easy.mybatis.multidatasource.mapper.UserMapper;
import com.easy.mybatis.multidatasource.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.io.Serializable;
import java.util.List; @Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper; /**
* 插入一条记录
*
* @param entity 实体对象
*/
@Override
public int insert(User entity) {
return userMapper.insert(entity);
} /**
* 根据 ID 删除
*
* @param id 主键ID
*/
@Override
public int deleteById(Serializable id) {
return userMapper.deleteById(id);
} /**
* 根据 ID 修改
*
* @param entity 实体对象
*/
@Override
public int updateById(User entity) {
return userMapper.updateById(entity);
} /**
* 根据 ID 查询
*
* @param id 主键ID
*/
@Master
@Override
public User selectById(Serializable id) {
return userMapper.selectById(id);
} @Override
public List<User> selectList() {
return userMapper.selectList();
}
}

这里我们注意到,本来selectById应该访问的是读库(slave库),我这里通过注解的方式,手动把它指到了可写库(master库)

10.最后我贴上yaml配置文件application.yml

# DataSource Config
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/easy_web?useSSL=false&serverTimezone=UTC
username: root
password: 123456
slave1:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/easy_web?useSSL=false&serverTimezone=UTC
username: root
password: 123456
slave2:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/easy_web?useSSL=false&serverTimezone=UTC
username: root
password: 123456

该配置文件配置了三个数据源(这里我为了方便,把三个数据源指到了同个库,实际生产环境会有不同的库和读写用户)

编写测试用例,查看执行结果,分析数据库调用情况

1.单元测试类MultiDataSourceServiceTest.java

package com.easy.mybatis.multidatasource;

import com.easy.mybatis.multidatasource.entity.User;
import com.easy.mybatis.multidatasource.service.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import javax.annotation.Resource;
import java.util.List; import static org.assertj.core.api.Assertions.assertThat; /**
* <p>
* 内置 CRUD 演示
* </p>
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
//指定单元测试按字母顺序执行
@FixMethodOrder(value = MethodSorters.NAME_ASCENDING)
public class MultiDataSourceServiceTest { @Resource
private IUserService userService; @Test
public void aInsert() {
User user = new User();
user.setId(20l);
user.setName("小羊");
user.setAge(3);
user.setEmail("abc@mp.com"); log.info("开始执行insert方法,id={}", user.getId());
assertThat(userService.insert(user));
// 成功直接拿会写的 ID
assertThat(user.getId()).isNotNull();
} @Test
public void bUpdate() {
User user = new User();
user.setId(20l);
user.setName("小羊update");
user.setAge(3);
user.setEmail("abc@mp.com");
log.info("开始执行updateById方法,id={}", user.getId());
assertThat(userService.updateById(user) > 0);
} @Test
public void cSelectById() {
int id = 20;
log.info("开始执行selectById方法,id={}", id);
log.info("数据为=={}", userService.selectById(id));
} @Test
public void dDelete() {
int id = 20;
log.info("开始执行deleteById方法,id={}", id);
assertThat(userService.deleteById(id));
} @Test
public void eSelectList() {
for (int i = 0; i < 5; i++) {
log.info("开始执行selectList方法,index={}", i);
List<User> list = userService.selectList();
log.info("查询到的数据为,list={}", list);
}
}
}

2.查看控制台执行结果

2019-08-29 16:36:04.684  INFO 13028 --- [           main] c.e.m.m.MultiDataSourceServiceTest       : Starting MultiDataSourceServiceTest on YHE6OR5UXQJ6D35 with PID 13028 (started by Administrator in E:\project\spring-boot-demo\mybatis-multi-datasource)
2019-08-29 16:36:04.685 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : No active profile set, falling back to default profiles: default
2019-08-29 16:36:08.172 INFO 13028 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-08-29 16:36:08.814 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : Started MultiDataSourceServiceTest in 4.85 seconds (JVM running for 5.918)
2019-08-29 16:36:09.008 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行insert方法,id=20
2019-08-29 16:36:09.018 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到master库
2019-08-29 16:36:09.054 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-08-29 16:36:09.256 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-08-29 16:36:09.547 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行updateById方法,id=20
2019-08-29 16:36:09.548 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到master库
2019-08-29 16:36:09.731 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectById方法,id=20
2019-08-29 16:36:09.732 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到master库
2019-08-29 16:36:10.213 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 数据为==User(id=20, name=小羊update, age=3, email=abc@mp.com)
2019-08-29 16:36:10.216 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行deleteById方法,id=20
2019-08-29 16:36:10.216 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到master库
2019-08-29 16:36:10.402 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectList方法,index=0
2019-08-29 16:36:10.403 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : slave库访问线程数==>0
2019-08-29 16:36:10.403 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到slave2库
2019-08-29 16:36:10.405 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2019-08-29 16:36:10.418 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
2019-08-29 16:36:10.422 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 查询到的数据为,list=[User(id=1, name=Jone, age=18, email=ab@c.c), User(id=2, name=mp, age=null, email=miemie2@baomidou.com), User(id=5, name=Billie, age=24, email=test5@tqlin.com)]
2019-08-29 16:36:10.422 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectList方法,index=1
2019-08-29 16:36:10.422 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : slave库访问线程数==>1
2019-08-29 16:36:10.422 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到slave1库
2019-08-29 16:36:10.422 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Starting...
2019-08-29 16:36:10.428 INFO 13028 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Start completed.
2019-08-29 16:36:10.429 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 查询到的数据为,list=[User(id=1, name=Jone, age=18, email=ab@c.c), User(id=2, name=mp, age=null, email=miemie2@baomidou.com), User(id=5, name=Billie, age=24, email=test5@tqlin.com)]
2019-08-29 16:36:10.429 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectList方法,index=2
2019-08-29 16:36:10.429 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : slave库访问线程数==>2
2019-08-29 16:36:10.429 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到slave2库
2019-08-29 16:36:10.431 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 查询到的数据为,list=[User(id=1, name=Jone, age=18, email=ab@c.c), User(id=2, name=mp, age=null, email=miemie2@baomidou.com), User(id=5, name=Billie, age=24, email=test5@tqlin.com)]
2019-08-29 16:36:10.431 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectList方法,index=3
2019-08-29 16:36:10.431 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : slave库访问线程数==>3
2019-08-29 16:36:10.431 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到slave1库
2019-08-29 16:36:10.432 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 查询到的数据为,list=[User(id=1, name=Jone, age=18, email=ab@c.c), User(id=2, name=mp, age=null, email=miemie2@baomidou.com), User(id=5, name=Billie, age=24, email=test5@tqlin.com)]
2019-08-29 16:36:10.432 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 开始执行selectList方法,index=4
2019-08-29 16:36:10.433 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : slave库访问线程数==>4
2019-08-29 16:36:10.433 INFO 13028 --- [ main] c.e.m.multidatasource.config.DBContext : 切换到slave2库
2019-08-29 16:36:10.435 INFO 13028 --- [ main] c.e.m.m.MultiDataSourceServiceTest : 查询到的数据为,list=[User(id=1, name=Jone, age=18, email=ab@c.c), User(id=2, name=mp, age=null, email=miemie2@baomidou.com), User(id=5, name=Billie, age=24, email=test5@tqlin.com)]
2019-08-29 16:36:10.444 INFO 13028 --- [ Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2019-08-29 16:36:10.446 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated...
2019-08-29 16:36:10.463 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed.
2019-08-29 16:36:10.463 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Shutdown initiated...
2019-08-29 16:36:10.497 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Shutdown completed.
2019-08-29 16:36:10.497 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2019-08-29 16:36:10.500 INFO 13028 --- [ Thread-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
  • 我们看到service的insert,updateById,deleteById方法执行的是可写库(master库)
  • 而selectById方法,因为我们在service使用注解的方式手动切到了master库,所以数据不会去slave库读取了(用来解决有些开发不规范,查询接口带有写库方法的情况)
  • selectList方法,我们循环调用了5次,看到结果成功在slave1和slave2轮询读取了

资料

Spring Boot MyBatis 数据库集群访问实现的更多相关文章

  1. spring boot 整合 quartz 集群环境 实现 动态定时任务配置【原】

    最近做了一个spring boot 整合 quartz  实现 动态定时任务配置,在集群环境下运行的 任务.能够对定时任务,动态的进行增删改查,界面效果图如下: 1. 在项目中引入jar 2. 将需要 ...

  2. Spring Boot Quartz 分布式集群任务调度实现

    Spring Boot Quartz 主要内容 Spring Scheduler 框架 Quartz 框架,功能强大,配置灵活 Quartz 集群 mysql 持久化定时任务脚本(tables_mys ...

  3. Spring Boot集成Redis集群(Cluster模式)

    目录 集成jedis 引入依赖 配置绑定 注册 获取redis客户端 使用 验证 集成spring-data-redis 引入依赖 配置绑定 注册 获取redis客户端 使用 验证 异常处理 同样的, ...

  4. Spring boot连接MongoDB集群

    主要问题是:MongoDB集群分为复制集(replicaSet)与分片集(shardingSet),那么如何去连接这两种集群: 参考官方文档,我使用了最通用的方法:通过构造connection str ...

  5. Spring Boot入门教程2-1、使用Spring Boot+MyBatis访问数据库(CURD)注解版

    一.前言 什么是MyBatis?MyBatis是目前Java平台最为流行的ORM框架https://baike.baidu.com/item/MyBatis/2824918 本篇开发环境1.操作系统: ...

  6. (45). Spring Boot MyBatis连接Mysql数据库【从零开始学Spring Boot】

    大家在开发的时候,会喜欢jdbcTemplate操作数据库,有喜欢JPA操作数据库的,有喜欢MyBatis操作数据库的,对于这些我个人觉得哪个使用顺手就使用哪个就好了,并没有一定要使用哪个,个人在实际 ...

  7. # spring boot + mybatis 读取数据库

    spring boot + mybatis 读取数据库 创建数据库 use testdb; drop table if exists t_city; create table t_city( id i ...

  8. Spring Boot + Mybatis多数据源和动态数据源配置

    文章转自 https://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种 ...

  9. spring boot + mybatis + layui + shiro后台权限管理系统

    后台管理系统 版本更新 后续版本更新内容 链接入口: springboot + shiro之登录人数限制.登录判断重定向.session时间设置:https://blog.51cto.com/wyai ...

随机推荐

  1. Surging实践经验

    背景 在去年9月份的时候,我入职一家做航空软件产品的公司.当时公司部门领导决定构建一个技术平台(或称为技术中台),通过该技术平台进而孵化各个业务系统.说白了就是需要通过一个分布式框架或是微服务框架提高 ...

  2. 【Java中级】(五)异常处理

    1.什么是异常 异常定义:导致程序的正常流程被中断的事件,叫做异常. 2.异常处理 try catch finally throws package exception; import java.io ...

  3. 【Arduino】66种传感器系列实验(1)---干簧管传感器模块

    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和各种模块,依照实践(动手试试)出真知的理念,以学习和交流为目的,这里 ...

  4. 分析了京东内衣销售记录,告诉你妹子们的真Size!

    >今天闲暇之余写了一个爬虫例子.通过爬虫去爬取京东的用户评价,通过分析爬取的数据能得到很多结果,比如,哪一种颜色的胸罩最受女性欢迎,以及中国女性的平均size(仅供参考哦~) 打开开发者工具-n ...

  5. python课堂整理10---局部变量与全局变量

    一.局部变量与全局变量 1. 没有缩进,顶头写的变量为全局变量 2. 在子程序里定义的变量为局部变量 3. 只有函数能把变量私有化 name = 'lhf' #全局变量 def change_name ...

  6. Java集合系列(一):集合的定义及分类

    1. 集合的定义 什么是集合呢? 定义:集合是一个存放对象的引用的容器. 在Java中,集合位于java.util包下. 2. 集合和数组的区别(面试常问) 提到容器,就会想起数组,那么集合和数组的区 ...

  7. JAVA-Spring框架之IOC(DI)(1)

    spring框架概述 spring是一个service层的框架,可以整合许多其 他框架,进行工作 spring的主要技术是IOC(DI)和AOP IOC(DI)-控制反转(依赖注入)AOP - 面向切 ...

  8. Anaconda大法好,为什么要用Anaconda(附linux安装与用例)

    距离写上一个博客已经过去很久了,注册的时候我还是个大三学生抱着windows系统的visual studio在OPENCV等等复杂组件下面瑟瑟发抖,一不小心就担心hpp找不到了,依赖库没了,或者安装了 ...

  9. c#小灶——初识c#

    提到c#,就不得不说.net,.net是微软开发的一个平台,简单来说,在这个平台上,可以编写.运行程序.可能很多人觉得这个平台离我们很遥远,其实不然,这个平台就一直在我们的windows操作系统里,默 ...

  10. Java 8 Stream实践

    [**前面的话**]Java中的Stream于1.8版本析出,平时项目中也有用到,今天就系统的来实践一下.下面借用重庆力帆队伍中我个人比较喜欢的球员来操作一波,队员的年龄为了便于展示某些api做了调整 ...