2.JDBC必知必会

news2025/7/23 17:14:01

文章目录

  • 2.0 JDBC
  • 2.1 配置单数据源
    • 项目一:尝试Spring Boot自动装配数据源
      • 创建
      • 查看应用程序有哪些bean
      • 原理
      • 问题
    • 项目二:SpringMVC手动配置数据源
    • Spring Boot自动装配原理与数据源相关配置
    • 项目三:尝试Spring Boot自动装配+参数配置
  • 2.2.配置多数据源
    • 1.排除Spring Boot依赖,使用Spring手工配置两组DataSource
    • 2.与SpringBoot结合
    • 留言
  • 2.3 HikariCP(日语:光)
    • HikariCP为什么快
    • Spring Boot 2.x 自动配置HikariCP作为数据源的源码
    • 常用HikariCP配置参数
    • 留言
  • 2.4 Alibaba Druid
    • 项目一:手动配置Druid数据源
    • 项目二:druid-spring-boot-starter自动配置
      • Druid Filter
      • Druid密码加密解密
  • 2.5 数据库连接池的选择
  • 2.6 Spring的JDBC操作类
    • spring-jdbc
    • 通过注解定义Bean
    • 简单的JDBC操作
      • JdbcTemplete
      • NamedParameterJdbcTemplate
      • 项目代码
        • 配置数据源参数
        • schema.sql
        • 单条语句执行
        • 实体类Foo
        • 批处理操作
        • 启动类
  • 2.7 Spring的事务抽象
    • 事务抽象的核心接口
      • PlatformTransactionManager(interface)
      • TransactionDefinition(class)
    • 事务的传播特性
    • 事务的隔离性
  • 2.8 事务抽象实例
    • 编程式事务
      • (1)PlatformTransactionManager
      • (2)transactionTemplate
    • 声明式事务
      • 基于注解的配置方式
      • 实例
  • 2.8 Spring的JDBC异常抽象
    • Spring是怎么认识错误码的
  • 2.9 课程答疑
    • 开发环境
      • (1)Lombok插件
      • (2)Maven Helper插件
      • (3)Cygwin
      • (4)Docker
    • Spring常用注解
      • (1)配置类与配置的注入
      • (2)Bean的定义
      • (3)注入相关
    • Actuator提供的Endpoint(端点)
    • 多数据源、读写分离、分库分表
      • 访问几个完全不同的数据库:配置两个完全不同的DataSource
      • 访问同一个库的主库与从库:主库做读写操作,从库做读操作
      • 访问一组做了分库分表的数据库
    • 事务
      • 常用传播特性
      • 测试代码
    • Alibaba Druid开启慢SQL日志
    • 注意事项

2.0 JDBC

JDBC小结

2.1 配置单数据源

项目一:尝试Spring Boot自动装配数据源

创建

  • 访问https://start.spring.io/
  • 增加Spring Boot Actuator(健康检查、beans),H2 Database(H2数据库驱动),JDBC API(简化JDBC使用),Lombok(@Slf4j、@Data),Spring Web依赖(访问actuator)。
package com.example.demo;

import ch.qos.logback.classic.Logger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootApplication
@Slf4j   // lombok
@Order(value = 2)
public class DemoApplication implements CommandLineRunner {

	@Autowired   // 先根据类型查找,再根据名称查找
	private DataSource dataSource;

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
	
	/**
	 * CommandLineRunner接口的run方法会在Spring Beans都初始化之后,
	 * SpringApplication.run() 之前执行,适合应用程序启动之初的数据初始化工作。
	 * 也可以用ApplicationRunner接口,只是run方法的参数是ApplicationArguments对象
	 * 可以用@Order(value = 1)指定执行顺序
	 */
	@Override
	public void run(String... args) throws Exception {
		showConnection();
	}

	private void showConnection() throws SQLException {
		log.info("====================================================");
		log.info(dataSource.toString());
		Connection conn = dataSource.getConnection();
		log.info("====================================================");
		log.info(conn.toString());
		conn.close();
	}
}
package com.example.SingleDataSource1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = 1)
@Slf4j
public class AAA implements CommandLineRunner {
    public void run(String... args) {
        log.info("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
    }
}

查看应用程序有哪些bean

访问(安装JSON Viewer插件):

http://localhost:8080/actuator/beans
"dataSource": {
          "aliases": [
            
          ],
          "scope": "singleton",
          "type": "com.zaxxer.hikari.HikariDataSource",
          "resource": "class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]",
          "dependencies": [
            "org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Hikari",
            "spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties"
          ]
        }

原理

spring-boot-starter-actuator会引入spring-boot-starter。
spring-boot-starter会引入spring-boot-autoconfigure。
spring-boot-autoconfigure中的org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration类会自动注入HikariDataSource。

问题

如何添加修改数据源的配置
如何手动配置数据源
如何配置多个数据源

项目二:SpringMVC手动配置数据源

配置依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>geektime.spring.data</groupId>
    <artifactId>pure-spring-datasource-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>5.1.3.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>RELEASE</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>
</project>

可以选择在配置文件中写bean

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName" value="org.h2.Driver"/>
    <property name="url" value="jdbc:h2:mem:testdb"/>
    <property name="username" value="SA"/>
    <property name="password" value="SA"/>
</bean>

或者用java代码生成

package geektime.spring.data.datasourcedemo;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Properties;

// 标识为配置类,相当于<beans></beans>,用于创建多个bean,类本身也会作为bean
@Configuration
// 在SpringBoot启动类上加上@EnableTransactionManagement 开启事务的注解
// service包下的实现类中的方法上面加上@Transactional
@EnableTransactionManagement
public class DataSourceDemo {
    @Autowired
    private DataSource dataSource;

    /* 配置数据源 */
    @Bean(destroyMethod = "close")
    public DataSource dataSource() throws Exception {
        Properties properties = new Properties();
        properties.setProperty("driverClassName", "org.h2.Driver");
        properties.setProperty("url", "jdbc:h2:mem:testdb");
        properties.setProperty("username", "sa");
        return BasicDataSourceFactory.createDataSource(properties);
    }

    /* 事务管理 */
    @Bean
    public PlatformTransactionManager transactionManager() throws Exception {
        return new DataSourceTransactionManager(dataSource());
    }

    public void showDataSource() throws SQLException {
        System.out.println(dataSource.toString());
        Connection conn = dataSource.getConnection();
        System.out.println(conn.toString());
        conn.close();
    }

    public static void main(String[] args) throws SQLException {
        ApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext*.xml");
        showBeans(applicationContext);
        dataSourceDemo(applicationContext);
    }

    private static void showBeans(ApplicationContext applicationContext) {
        System.out.println(Arrays.toString(applicationContext.getBeanDefinitionNames()));
    }

    private static void dataSourceDemo(ApplicationContext applicationContext) throws SQLException {
        DataSourceDemo demo = applicationContext.getBean("dataSourceDemo", DataSourceDemo.class);
        demo.showDataSource();
    }
}

Spring Boot自动装配原理与数据源相关配置

在这里插入图片描述
在这里插入图片描述

项目三:尝试Spring Boot自动装配+参数配置

SpringBoot默认采⽤资源根⽬录下schema.sql⽂件进⾏创建表的初始化,使⽤data.sql进⾏插⼊初始化数据的⼯作。
在这里插入图片描述
data.sql:

INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa');
INSERT INTO FOO (ID, BAR) VALUES (2, 'bbb');

schema.sql:

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

配置文件:

spring.datasource.url=jdbc:h2:mem:tested
spring.datasource.username=root
spring.datasource.password=123456

spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idleTimeout=600000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.maxLifetime=1800000

#------初始化内嵌数据库(springboot不配置以下内容,也会自动加载以下配置)-------
spring.datasource.initialization-mode=always
# 指定Schema (DDL)脚本
spring.datasource.schema=classpath:schema.sql
# 指定Data (DML)脚本
spring.datasource.data=classpath:data.sql
# 指定schema要使用的Platform
spring.datasource.platform=h2

# 是否启用h2控制台
spring.h2.console.enabled=true
# 配置h2控制台访问地址,http://localhost:8080/h2
spring.h2.console.path=/h2
package geektime.spring.data.datasourcedemo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;

@SpringBootApplication
@Slf4j
public class DataSourceDemoApplication implements CommandLineRunner {
	@Autowired
	private DataSource dataSource;

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(DataSourceDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		showConnection();
		showData();
	}

	private void showConnection() throws SQLException {
		log.info(dataSource.toString());
		Connection conn = dataSource.getConnection();
		log.info(conn.toString());
		conn.close();
	}

	private void showData() {
		List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT * FROM FOO");
		log.info("========================================================");
		for (Map m : maps) {
			log.info("" + m);
		}
//				.forEach(row -> log.info(row.toString()));
	}
}

2.2.配置多数据源

(1)不同数据源的配置要分开(即使数据源之间有很多配置是相同的)
(2)告诉系统、对应的设施(事务,ORM)使用哪个DataSource

1.排除Spring Boot依赖,使用Spring手工配置两组DataSource

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>DoubleDataSource1</artifactId>
	<version>0.0.1-SNAPSHOT</version>


	<properties>
		<spring.version>5.1.3.RELEASE</spring.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>RELEASE</version>
			<scope>runtime</scope>
		</dependency>
	</dependencies>

</project>

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.example" />
</beans>
package com.example.DoubleDataSource1;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
public class DoubleDataSource1Application {
	@Autowired
	@Qualifier("dataSource1")  // 当多个bean类名冲突,名字无法判断时,根据指定的别名注入
	DataSource dataSource;

	@Bean(destroyMethod = "close")
	public DataSource dataSource1() throws Exception {
		Properties properties = new Properties();
		properties.setProperty("driverClassName", "org.h2.Driver");
		properties.setProperty("url", "jdbc:h2:mem:testdb1");
		properties.setProperty("username", "sa");
		return BasicDataSourceFactory.createDataSource(properties); // dbcp
	}

	@Bean(destroyMethod = "close")
	public DataSource dataSource2() throws Exception {
		Properties properties = new Properties();
		properties.setProperty("driverClassName", "org.h2.Driver");
		properties.setProperty("url", "jdbc:h2:mem:testdb2");
		properties.setProperty("username", "sa");
		return BasicDataSourceFactory.createDataSource(properties); // dbcp
	}

	@Bean
	public PlatformTransactionManager transactionManager1() throws Exception {
		return new DataSourceTransactionManager(dataSource1());
	}

	@Bean
	public PlatformTransactionManager transactionManager2() throws Exception {
		return new DataSourceTransactionManager(dataSource2());
	}

	public void showDataSource1() throws SQLException {
		System.out.println(dataSource1.toString());
		Connection conn = dataSource1.getConnection();
		System.out.println(conn.toString());
		conn.close();
	}

	public void showDataSource2() throws SQLException {
		System.out.println(dataSource2.toString());
		Connection conn = dataSource2.getConnection();
		System.out.println(conn.toString());
		conn.close();
	}
	
	public static void main(String[] args) throws Exception {
		ApplicationContext applicationContext =
				new ClassPathXmlApplicationContext("applicationContext*.xml");
		showBeans(applicationContext);
		dataSourceDemo(applicationContext);
	}
	
	private static void showBeans(ApplicationContext applicationContext) {
		System.out.println(Arrays.toString(applicationContext.getBeanDefinitionNames()));
	}

	private static void dataSourceDemo(ApplicationContext applicationContext) throws SQLException {
		DoubleDataSource1Application demo = applicationContext.getBean("doubleDataSource1Application", DoubleDataSource1Application.class);
	}
}

2.与SpringBoot结合

SpringBoot的自动配置多数都是针对只有一个DataSource的,所以我在课程中提了,要么给主要的DataSource Bean增加@Primary注解,要么就把几个自动配置类排除掉。
(1)配置@Primary类型的Bean(两个Bean有主次之分)
(2)排除Spring Boot的自动配置,通过spring-boot-autoconfigure手动构建数据源(没有主次之分)(与使用Sring手动配置相比优点是:参数写在properties里,同一套代码使用多个数据库连接池)

package com.example.DoubleDataSource2;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.Connection;
import java.sql.SQLException;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
		DataSourceTransactionManagerAutoConfiguration.class,
		JdbcTemplateAutoConfiguration.class})
@Slf4j
public class DoubleDataSource2Application implements CommandLineRunner {

	@Autowired
	HikariDataSource fooDataSource;

	@Autowired
	BasicDataSource barDataSource;

	@Autowired
	RuoYiConfig ruoYiConfig;

	public static void main(String[] args) {
		SpringApplication.run(DoubleDataSource2Application.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		showConnection();
	}

	public void showConnection() throws SQLException {
		log.info("============================================开始");
		log.info(fooDataSource.toString());
		log.info("poolname:" + fooDataSource.getPoolName());
		Connection conn1 = fooDataSource.getConnection();
		log.info(conn1.toString());
		conn1.close();
		log.info("============================================");
		log.info(barDataSource.toString());
		log.info("username:" + barDataSource.getUsername());
		Connection conn2 = barDataSource.getConnection();
		log.info(conn2.toString());
		conn2.close();
		log.info("============================================");
		log.info("" + ruoYiConfig.getName() + "," + ruoYiConfig.getVersion());
		log.info("============================================结束");
	}
}
package com.example.DoubleDataSource2;

import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@Slf4j
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("foo.datasource")
    public DataSourceProperties fooDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.hikari")
    public HikariDataSource fooDataSource() {
        DataSourceProperties dataSourceProperties = fooDataSourceProperties();
        return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    @Resource
    public PlatformTransactionManager fooTxManager(DataSource fooDataSource) {
        return new DataSourceTransactionManager(fooDataSource);
    }

    @Bean
    @Resource
    public JdbcTemplate foojdbcTemplate(DataSource fooDataSource) {
        return new JdbcTemplate(fooDataSource);
    }


    @Bean
    @ConfigurationProperties("bar.datasource")
    public DataSourceProperties barDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.dbcp2")
    public BasicDataSource barDataSource() {
        DataSourceProperties dataSourceProperties = barDataSourceProperties();
        return dataSourceProperties.initializeDataSourceBuilder().type(BasicDataSource.class).build();
    }

    @Bean
    @Resource
    public PlatformTransactionManager barTxManager(DataSource barDataSource) {
        return new DataSourceTransactionManager(barDataSource);
    }

    @Bean
    @Resource
    public JdbcTemplate barjdbcTemplate(DataSource barDataSource) {
        return new JdbcTemplate(barDataSource);
    }
}

使用@ConfigurationProperties(prefix = “ruoyi”)从配置文件获取参数

package com.example.DoubleDataSource2;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "ruoyi")
public class RuoYiConfig
{
    public String name;

    public String version;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}
management.endpoints.web.exposure.include=*
spring.output.ansi.enabled=ALWAYS

foo.datasource.url=jdbc:h2:mem:foo
foo.datasource.username=sa
foo.datasource.password=123456
spring.datasource.hikari.poolName=hikari-aaaa

bar.datasource.url=jdbc:h2:mem:bar
bar.datasource.username=Root
bar.datasource.password=123456
spring.datasource.dbcp2.userName=Root

ruoyi.name = Han
ruoyi.version = 1.1.0

增加dbcp2依赖

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-dbcp2</artifactId>
	<version>RELEASE</version>
</dependency>

留言

Bean简单来讲就是由Spring容器创建并托管的实例。
@Resource是说我这个方法的参数要按照名字来注入其他的Bean。

2.3 HikariCP(日语:光)

HikariCP为什么快

(1)字节码级别优化(很多方法通过JavaAssist生成)。
(2)大量的小改进。用FastStatementList代替ArrayList,无锁集合ConcurrentBag,代理类的优化(用invokestatic代替了invokervirtual)

Spring Boot 2.x 自动配置HikariCP作为数据源的源码

Spring Boot 1.x 默认使用Tomcat连接池,因此需要移除tomcat-jdbc依赖,同时引入HikariCP依赖,spring.datasource.type=com.zaxxer.hikari.HikariDataSource指定数据源类型

Spring Boot 2.x 默认使用 HikariCP。配置 spring.datasource.hikari.*
查看spring-boot-autoconfigure2.6.7.jar 中 DataSourceConfiguration.class源码

@Configuration(proxyBeanMethods = false)
// class Path中有HikariDataSource.class
@ConditionalOnClass({HikariDataSource.class}) 
// Spring上下文中没有配置DataSource的Bean
@ConditionalOnMissingBean({DataSource.class})
@ConditionalOnProperty(
    name = {"spring.datasource.type"},
    havingValue = "com.zaxxer.hikari.HikariDataSource",
    matchIfMissing = true
)
static class Hikari {
    Hikari() {
    }

    @Bean
    @ConfigurationProperties(
        prefix = "spring.datasource.hikari"
    )
    HikariDataSource dataSource(DataSourceProperties properties) {
        HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }

        return dataSource;
    }
}

常用HikariCP配置参数

spring.datasource.url=jdbc:h2:mem:tested
spring.datasource.username=root
spring.datasource.password=123456

spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=600000  
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1800000

留言

数据库连接池应该设多大?
连接数 = ((核心数 * 2) + 有效磁盘数)
https://zhuanlan.zhihu.com/p/133996025

利用@ConfigurationProperties将属性的赋值放到配置文件中
https://blog.csdn.net/u014486725/article/details/124090043
https://blog.csdn.net/YooFale/article/details/84869869

2.4 Alibaba Druid

详细的监控
ExceptionSorter,针对主流数据库的返回码都有支持
SQL防注入
内置加密配置
众多拓展点,方便进行定制

项目一:手动配置Druid数据源

导入依赖

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.10</version>
</dependency>

创建Druid数据源bean

package com.example.DruidDemo;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.annotation.Resource;

@Configuration
public class DruidConfig {

    @Bean(name = "druidDataSource")
    public DruidDataSource druidDataSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        dataSource.setUrl("jdbc:h2:mem:foo");
        dataSource.setUsername("Sa");
        dataSource.setPassword("n/z7PyA5cvcXvs8px8FVmBVpaRyNsvJb3X7YfS38DJrIg25EbZaZGvH4aHcnc97Om0islpCAPc3MqsGvsrxVJw==");

        dataSource.setInitialSize(5);
        dataSource.setMaxActive(5);
        dataSource.setMinIdle(5);

        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    @Resource
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource) {
        return new JdbcTemplate(druidDataSource);
    }
}

package com.example.DruidDemo;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.Connection;

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@Slf4j
public class DruidDemoApplication implements CommandLineRunner {

	@Autowired
	DruidDataSource druidDataSource;

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(DruidDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		log.info("=========================================");
		log.info(druidDataSource.getUrl());
		log.info(druidDataSource.getUsername());
		log.info(druidDataSource.getPassword());
		log.info("" +druidDataSource.getInitialSize());
		log.info("" + druidDataSource.getMaxActive());
		log.info("" + druidDataSource.getMinIdle());
		log.info(druidDataSource.toString());  // 没有创建
		DruidPooledConnection connection = druidDataSource.getConnection();
		log.info(druidDataSource.toString());  // 第一次使用的时候Druid会自己调用init()
	}
}

项目二:druid-spring-boot-starter自动配置

spring-boot-autoconfigure不支持对druid自动配置

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
	<exclusions>
		<exclusion>  // 排除HikariCP
			<artifactId>HikariCP</artifactId>
			<groupId>com.zaxxer</groupId>
		</exclusion>
	</exclusions>
</dependency>

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.10</version>
</dependency>
@SpringBootApplication
@Slf4j
public class DruidDemoApplication implements CommandLineRunner {

	@Autowired
	DataSource dataSource;

	@Autowired
	private JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(DruidDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		log.info("=========================================");
		log.info(dataSource.toString());
	}
}

Druid Filter

  • 用于定制连接池操作的各种环节
  • 可以继承FilterEventAdapter以便方便地实现Filter
  • 在resources目录下增加META-INF/druid-filter.properties文件(配置filter类地址和对应地名称)
    在这里插入图片描述
@Slf4j
public class ConnectionLogFilter extends FilterEventAdapter {  // 继承FilterEventAdapter 

    @Override
    // 连接前打日志
    public void connection_connectBefore(FilterChain chain, Properties info) {
        log.info("BEFORE CONNECTION!");
    }

    @Override
    // 连接后打日志
    public void connection_connectAfter(ConnectionProxy connection) {
        log.info("AFTER CONNECTION!");
    }
}
spring.output.ansi.enabled=ALWAYS

spring.datasource.url=jdbc:h2:mem:foo
spring.datasource.username=sa

# 密码加密
spring.datasource.druid.filter.config.enabled=true
// 加密后地密码
spring.datasource.password=n/z7PyA5cvcXvs8px8FVmBVpaRyNsvJb3X7YfS38DJrIg25EbZaZGvH4aHcnc97Om0islpCAPc3MqsGvsrxVJw==
// 解密用的公钥
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${public-key}
// 公钥
public-key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALS8ng1XvgHrdOgm4pxrnUdt3sXtu/E8My9KzX8sXlz+mXRZQCop7NVQLne25pXHtZoDYuMh3bzoGj6v5HvvAQ8CAwEAAQ==

# Filter配置
# conn是自己实现地filter, config可以实现密码加解密功能, stat统计功能, 日志通过slf4j输出
spring.datasource.druid.filters=conn,config,stat,slf4j

# SQL防注入
spring.datasource.druid.filter.wall.enabled=true
spring.datasource.druid.filter.wall.db-type=h2 
spring.datasource.druid.filter.wall.config.delete-allow=false   // 不能做删除操作
spring.datasource.druid.filter.wall.config.drop-table-allow=false    // 不能做删表操作

# 连接池属性
spring.datasource.druid.initial-size=5
spring.datasource.druid.max-active=5
spring.datasource.druid.min-idle=5

# 做检查
spring.datasource.druid.test-on-borrow=true
spring.datasource.druid.test-on-return=true
spring.datasource.druid.test-while-idle=true

# 配置可执行sql
spring.datasource.druid.filter.slf4j.enabled=true
spring.datasource.druid.filter.slf4j.statementExecutableSqlLogEnable=true
# 把druid.sql的日志级别设置为DEBUG
logging.level.druid.sql=debug

Druid密码加密解密

String password = "123456";
String[] arr = ConfigTools.genKeyPair(512);   // 获取私钥密钥
LOG.debug("privateKey:" + arr[0]);
LOG.debug("publicKey:" + arr[1]);
LOG.debug("password:" + ConfigTools.encrypt(arr[0], password));   // 用私钥对密码加密

https://vimsky.com/examples/detail/java-class-com.alibaba.druid.filter.config.ConfigTools.html

2.5 数据库连接池的选择

可靠性
性能
功能(SQL防注入)
可运维性(密码加密)
可拓展性(服务追踪中,执行SQL前打印TraceId和SpanId)
其他

2.6 Spring的JDBC操作类

spring-jdbc

  • core,JdbcTemplete等相关核心接口和类(RowMapper)
  • datasource,数据源相关的辅助类
  • object,将基本的JDBC操作封装成对象
  • support,错误码等其他辅助工具

通过注解定义Bean

  • @Componet 通用的Bean
  • @Repository 对数据库的操作
  • @Service 对业务的服务
  • @Controller
    • @RestController 方便开发RESTFUL Web Service

简单的JDBC操作

JdbcTemplete

  • query
  • queryForObject
  • queryForList
  • queryForMap
  • update 插入、删除、修改
  • execute 通用方法

NamedParameterJdbcTemplate

在使用JdbcTemplate的使用实例中,SQL语句中的参数占位符都是?,当参数只有一个的时候,也许我们看不出什么问题,但是,当参数有了多个,很容易搞错参数的赋值顺序。
为了解决这个问题,Spring JDBC提供了NamedParameterJdbcTemplate,使用命名化的参数来替代原先的问号占位符。

项目代码

配置数据源参数

management.endpoints.web.exposure.include=health,beans

spring.datasource.url=jdbc:h2:mem:tested
spring.datasource.username=root
spring.datasource.password=123456

spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idleTimeout=600000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.maxLifetime=1800000

#------初始化内嵌数据库(springboot不配置以下内容,也会自动加载以下配置)-------
spring.datasource.initialization-mode=always
# 指定Schema (DDL)脚本
spring.datasource.schema=classpath:schema.sql
# 指定Data (DML)脚本
spring.datasource.data=classpath:data.sql
# 指定schema要使用的Platform
spring.datasource.platform=h2

# 是否启用h2控制台
spring.h2.console.enabled=true
# 配置h2控制台访问地址,http://localhost:8080/h2
spring.h2.console.path=/h2

schema.sql

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

单条语句执行

@Repository
@Slf4j
public class FooDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 查询操作
     */
    public void queryData() {
        // 返回一行、一列
        log.info("==================Count: {}", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO", Long.class));

        // 返回一行、多列
        List<String> list = jdbcTemplate.queryForList("SELECT BAR FROM FOO", String.class);
        list.forEach(s -> log.info("==================Bar: {}", s));

        // 返回多行、多列,结果转换为Map
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT ID, BAR FROM FOO");
        log.info(maps.toString());
        maps.forEach(m -> {
            log.info("==================id-{}, bar-{}", m.get("ID"), m.get("BAR"));
        });

        // 返回多行、多列,结果映射为对象
        List<Foo> fooList = jdbcTemplate.query("SELECT * FROM Foo", new RowMapper<Foo>() {
            @Override
            public Foo mapRow(ResultSet rs, int rowNum) throws SQLException {
                return Foo.builder()
                        .id(rs.getLong(1))
                        .bar(rs.getString(2))
                        .build();
            }
        });
        fooList.forEach(f -> log.info("==================Foo: {}", f));
    }

    /**
     * 插入操作
     */
    public void insertData() {
        Arrays.asList("insert1", "insert2").forEach(bar -> {
            jdbcTemplate.update("INSERT INTO FOO (BAR) VALUES (?)", bar);
        });
    }
}

实体类Foo

@Data  
@Builder
public class Foo {
    private long id;
    private String bar;
}

批处理操作

@Repository
public class BatchFooDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void batchInsert() {
        ArrayList<String> list1 = new ArrayList<>(Arrays.asList("batch1", "batch2"));
        jdbcTemplate.batchUpdate("INSERT INTO FOO (BAR) VALUES (?)",
                new BatchPreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, list1.get(i));
                    }

                    @Override
                    public int getBatchSize() {
                        return list1.size();
                    }
                });

        List<Foo> list = new ArrayList<>();
        list.add(Foo.builder().id(100L).bar("batch3").build());
        list.add(Foo.builder().id(101L).bar("batch4").build());
        namedParameterJdbcTemplate
                .batchUpdate("INSERT INTO FOO (ID, BAR) VALUES (:id, :bar)",
                SqlParameterSourceUtils.createBatch(list));
    }
}

启动类

@SpringBootApplication
@Slf4j
public class SingleDataSource1Application implements CommandLineRunner {
	@Autowired
	private FooDao fooDao;

	@Autowired
	private BatchFooDao batchFooDao;

	public static void main(String[] args) {
		SpringApplication.run(SingleDataSource1Application.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		fooDao.insertData();
		batchFooDao.batchInsert();
		fooDao.queryData();
	}
}

2.7 Spring的事务抽象

Spring提供了一致的事务模型:
不管是使用JBDC、Hibernate还是Mybatis来操作数据,也不管使用的是DataSoure还是JTA的事务。在事务抽象里都能很好的把它统一在一起。

事务抽象的核心接口

PlatformTransactionManager(interface)

  • DataSourceTransactionManager(class)
  • HibernateTransactionManager(class)
  • JtaTransactionManager(class)
public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

TransactionDefinition(class)

通过TransactionDefinition可以取得TransactionStatus。

  • Propagation 传播特性
  • Isolation 隔离性
  • Timeout 事务的超时
  • Read-only status 是否是只读

事务的传播特性

默认是REQUIRED级别。
在这里插入图片描述

事务的隔离性

在这里插入图片描述

2.8 事务抽象实例

编程式事务

(1)PlatformTransactionManager

@SpringBootApplication
@Slf4j
public class SingleDataSource1Application implements CommandLineRunner {
	@Autowired
	JdbcTemplate jdbcTemplate;

	@Autowired
	PlatformTransactionManager platformTransactionManager;
	
	public static void main(String[] args) {
		SpringApplication.run(SingleDataSource1Application.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		/* 事务定义类 */
		DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
		transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		/* 返回事务对象 */
		TransactionStatus status = platformTransactionManager.getTransaction(transactionDefinition);

		try {
			log.info("====================================================");
			log.info("COUNT BEFORE TRANSACTION: {}", getCount());

			jdbcTemplate.update("INSERT INTO FOO (BAR) VALUES ('AAAA')");
			/* BAR1会报错 */
			jdbcTemplate.update("INSERT INTO FOO (BAR1) VALUES ('BBBB')");
			platformTransactionManager.commit(status);
		} catch (Exception e) {
			platformTransactionManager.rollback(status);
		}
		log.info("COUNT AFTER TRANSACTION: {}", getCount());
		log.info("====================================================");
	}

	private long getCount() {
		Long query = jdbcTemplate.queryForObject("SELECT COUNT(*) AS CNT FROM FOO", Long.class);
		return query;
	}
}

(2)transactionTemplate

public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
	/** 
	 * 有返回值:TransactionCallback
	 * 没有返回值:TransactionCallbackWithoutResult
	 */
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		.....
	}
}
@SpringBootApplication
@Slf4j
public class SingleDataSource1Application implements CommandLineRunner {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Autowired
	private TransactionTemplate transactionTemplate;

	public static void main(String[] args) {
		SpringApplication.run(SingleDataSource1Application.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		log.info("====================================================");
		log.info("COUNT BEFORE TRANSACTION: {}", getCount());
		transactionTemplate.execute(new TransactionCallbackWithoutResult() {
			@Override
			protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
				try {
					jdbcTemplate.update("INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa')");
					log.info("COUNT IN TRANSACTION: {}", getCount());
					throw new Exception();
				} catch (Exception e) {
					transactionStatus.setRollbackOnly();
				}
			}
		});
		log.info("COUNT AFTER TRANSACTION: {}", getCount());
		log.info("====================================================");
	}

	private long getCount() {
		Long query = jdbcTemplate.queryForObject("SELECT COUNT(*) AS CNT FROM FOO", Long.class);
		return query;
	}
}

声明式事务

Spring AOP:Java默认的动态代理方式做AOP。封装了AspectJ。

Spring的声明式事务是利用了AOP的Proxy,它在目标方法上做了一个封装。操作开始前启动事务,方法执行结束之后commit或者rollback。

基于注解的配置方式

@EnableTransactionManagement注解在Spring Boot工程中可以不用加

proxyTargetClass:false——基于接口的代理(JDK)。true——基于类的代理(CGLIB)。SpringBoot中有很多自动配置的,已经把proxyTargetClass设置为true了。
order:指定事务AOP的拦截的顺序,默认是最低的优先级。

在类上加@Transacational注解,则类里的public方法都会带上事务。而且属性都是用同一个。
在这里插入图片描述

实例

需要调用代理类才能执行到被代理增强的那些方法,如果是在方法内部调用的话,因为invokeInsertThenRollback本身是没有事务的,因此调用insertThenRollBack也不会有事务支持。

Spring Bean注入给其他人时,注入的不是A,而是B。所以通过Bean来调用时,调用的是B.xxx(),在A自身内部调用内部方法时,调用的还是A.xxx()

在自动注入的Bean上面加上@Lazy注解,防止循环依赖。

public interface FooService {
    void insertRecord();
    void insertThenRollback() throws RollbackException;
    void invokeInsertThenRollback() throws RollbackException;
}
@Component
public class FooServiceImpl implements FooService {
    @Autowired
    JdbcTemplate jdbcTemplate;
	
	@Autowired
	@Lazy
    FooService fooService;

    @Override
    @Transactional
    public void insertRecord() {
        jdbcTemplate.update("INSERT INTO FOO (BAR) VALUES ('AAA')");  // 插入一条
    }

    @Override
    @Transactional(rollbackFor = RollBackException.class)
    public void insertThenRollBack() throws RollBackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");  // 被回滚
        throw new RollBackException();
    }

    @Override
    public void invokeInsertThenRollback() throws RollBackException {
        /* 同级无法创建代理对象. 插入一条 */
        insertThenRollBack();
        /* (1)直接调用注入的实例,回滚 */
//        fooService.insertThenRollBack();
        /* (2)获取当前类的代理对象,再调用的代理对象的方法(其实是增强后的方法),回滚 */
//        FooService fooService = (FooService) AopContext.currentProxy();
//        fooService.insertThenRollBack();
    }
}
public class RollBackException extends Exception {
}
@SpringBootApplication
@Slf4j
public class SingleDataSource1Application implements CommandLineRunner {
	@Autowired
	FooService fooService;

	@Autowired
	JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(SingleDataSource1Application.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		fooService.insertRecord();
		log.info("AAA {}", getCount());

		try {
			fooService.insertThenRollBack();
		} catch (Exception e) {
			log.info("BBB {}", getCount());
		}

		try {
			fooService.invokeInsertThenRollback();
		} catch (Exception e) {
			log.info("BBB {}", getCount());
		}
	}

	private long getCount() {
		return jdbcTemplate.queryForObject("SELECT COUNT(*) AS CNT FROM FOO", Integer.class);
	}
}

2.8 Spring的JDBC异常抽象

Spring会将数据库操作的异常操作转换为DataAccessException。
无论使用何种数据访问方式,都能使用一样的异常。

Spring是怎么认识错误码的

(1)通过SQLErrorCodeSQLExceptionTranslator类解析错误码

(2)在org/springframework/jdbc/support/sql-error-codes.xml定义各个数据库的errorCode。可以自己在classpath下定义sql-error-codes.xml,会覆盖掉官方文件

resources下新增sql-error-codes.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
    <bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="badSqlGrammarCodes">
            <value>42000,42001,42101,42102,42111,42112,42121,42122,42132</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>23001,23505</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>22001,22003,22012,22018,22025,23000,23002,23003,23502,23503,23506,23507,23513</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>90046,90100,90117,90121,90126</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>50200</value>
        </property>
        <property name="customTranslations">
            <bean class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation">
                <property name="errorCodes" value="23001,23505" />
                <property name="exceptionClass"
                          value="com.example.SingleDataSource1.CustomDuplicatedKeyException" />
            </bean>
        </property>
    </bean>
</beans>
package com.example.SingleDataSource1;

import org.springframework.dao.DuplicateKeyException;

public class CustomDuplicatedKeyException extends DuplicateKeyException {
    public CustomDuplicatedKeyException(String msg) {
        super(msg);
    }

    public CustomDuplicatedKeyException(String msg, Throwable cause) {
        super(msg, cause);
    }
}

package com.example.SingleDataSource1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class SingleDataSource1ApplicationTests {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Test(expected = CustomDuplicatedKeyException.class)
	public void testThrowingCustomException() {
		jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'a')");
		jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'b')");
	}

}

package com.example.SingleDataSource1;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
@Slf4j
public class SingleDataSource1Application {

	public static void main(String[] args) {
		SpringApplication.run(SingleDataSource1Application.class, args);
	}
}

2.9 课程答疑

开发环境

(1)Lombok插件

如果IDEA版本在2020.3以上,不需要安装Lombok插件。

(2)Maven Helper插件

(3)Cygwin

(4)Docker

用于在本地启动一些演示用的依赖设施,MongoDB或Redis

Spring常用注解

(1)配置类与配置的注入

  • @Configuration:标注一个类是配置类
  • @Bean:将方法的返回对象变为Bean
  • @ConfigurationProperties:将配置文件中的变量注入返回对象中
  • @PropertySource:引入*.Properties或者 .yml
  • @ImportResource:将xml配置文件中的bean加载到Application Context中。
/** 
 - 写在启动类上
 */
@SpringBootApplication
@ImportResource({"classpath*:applicationContext.xml"})
public class ProductApplication {
}
/** 
 - 创建一个单独的配置类来加载此XML bean定义文件。
 */
@Configuration
@ImportResource({"classpath*:applicationContext.xml"})
public class XmlConfiguration {
}
  • @ComponentScan:
    @SpringBootApplication包含了@ComponentScan
    默认自动扫描加载启动类所在包及其子包。因此下图中A1、A2能加载到,B1加载不到
    在这里插入图片描述
    将B1也加入路径
@SpringBootApplication
@ComponentScan({"com.demo.zhujie1.aaaaa", "com.demo.zhujie1.bbbbb"})  // 加入B2路径,A1、A2路径也需要
@Slf4j
public class Zhujie1Application implements CommandLineRunner {
    @Autowired
    A1 a1;

    @Autowired
    A2 a2;

    @Autowired
    B1 b1;

    public static void main(String[] args) {
        SpringApplication.run(Zhujie1Application.class, args);
    }

    @Override
    public void run(String... args) {
        log.info(a1.getName());
        log.info(a2.getName());
        log.info(b1.getName());
    }
}

(2)Bean的定义

  • @Component
  • @Repository
  • @Service
  • @Controller
    • @RestController

(3)注入相关

  • @Autowired
  • @Qualifier
  • @Resource
  • @Value

Actuator提供的Endpoint(端点)

  • /actuator:展示所有端点
  • /actuator/health:健康检查
  • /actuator/info
  • /actuator/beans:查看Beans
  • /actuator/mappings:Web的URL映射
  • /actuator/env:环境信息
  • /actuator/conditions:查看代码某个配置在什么条件下生效
  • /actuator/threaddump:当前线程活动的快照

(1)默认:
/actuator/health和/actuator/info可Web访问

(2)解禁所有Endpoint:
management.endpoints.web.exposure.include=*

(3)开启/actuator/shutdown:

management.endpoint.shutdown.enabled=true
curl -X POST "http://localhost:8080/actuator/shutdown"  

多数据源、读写分离、分库分表

https://baijiahao.baidu.com/s?id=1687119939271468578&wfr=spider&for=pc

访问几个完全不同的数据库:配置两个完全不同的DataSource

在这里插入图片描述

访问同一个库的主库与从库:主库做读写操作,从库做读操作

配置三个数据源(缺点是主从复制有延迟)。
在这里插入图片描述
使用中间件:淘宝TDDL,ShardingSphere client版本。
在这里插入图片描述
使用缓存:
在这里插入图片描述

访问一组做了分库分表的数据库

  • 垂直分表:热门数据、冷门数据分开存储,大字段放在冷门数据表中。
  • 垂直分库:按业务拆分,放到不同的库中,这些库分别部署在不同的服务器,解决单一服务器性能的- 瓶颈,同时提升整体架构的业务清晰度。
  • 水平分表:解决单一表数据量过大的问题
  • 水平分库:解决单一服务器数据量过大的问题

使用ShardingSphere

事务

Spring的声明式事务本质上是通过AOP增强了类的功能。
Spring的AOP本质上就是为了类做了一个代理,看似在调用自己写的类,实际用的是增强后的代理类。

常用传播特性

  • REQUIRED:都在同一个事务中
  • REQUIRES_NEW:父子之间事务不影响
  • NOT_SUPPORTED:子方法以非事务方式运行
  • NESTED:起两个事务,父事务异常,子事务也异常
    在这里插入图片描述

测试代码

@Component
@Slf4j
public class FooServiceImpl implements FooService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    FooService fooService;

    @Override
    @Transactional(rollbackFor = RollbackException.class, propagation = Propagation.REQUIRED)
    public void insertThenRollBack() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
        throw new RollbackException();
    }

    @Override
    @Transactional(rollbackFor = RollbackException.class)
    public void invokeInsertThenRollback() throws RollbackException {
        jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('AAA')");
        try {
            fooService.insertThenRollBack();
        } catch (RollbackException e) {
        }
//        throw new RollbackException();
    }
}
@SpringBootApplication
@EnableTransactionManagement(mode = AdviceMode.PROXY)  // 启动类上开启事务,默认开启
@Slf4j
public class DeclarativeTransactionDemoApplication implements CommandLineRunner {
	@Autowired
	FooService fooService;

	@Autowired
	JdbcTemplate jdbcTemplate;

	public static void main(String[] args) {
		SpringApplication.run(DeclarativeTransactionDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		try {
			fooService.invokeInsertThenRollback();
		} catch (Exception e) {

		}

		log.info("AAA: {}", getCount1());
		log.info("BBB: {}", getCount2());
	}

	private long getCount1() {
		return (long) jdbcTemplate.queryForList("SELECT COUNT(*) AS CNT FROM FOO WHERE BAR = 'AAA'")
				.get(0).get("CNT");
	}

	private long getCount2() {
		return (long) jdbcTemplate.queryForList("SELECT COUNT(*) AS CNT FROM FOO WHERE BAR = 'BBB'")
				.get(0).get("CNT");
	}
}

Alibaba Druid开启慢SQL日志

@Repository
public class FooService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional
    public void selectForUpdate() {
    	// 在where 后面查询条件是主键索引,唯一索引时候是行锁。查询条件是普通字段时候加的是表锁
        jdbcTemplate.queryForObject("select id from foo where id = 1 for update", Long.class);
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
    }
}
@SpringBootApplication
@Slf4j
public class DruidDemoApplication implements CommandLineRunner {

	@Autowired
	DataSource dataSource;

	@Autowired
	private FooService fooService;

	public static void main(String[] args) {
		SpringApplication.run(DruidDemoApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		log.info("=========================================");
		new Thread(() -> fooService.selectForUpdate()).start();
		new Thread(() -> fooService.selectForUpdate()).start();
	}
}

spring.output.ansi.enabled=ALWAYS

spring.datasource.url=jdbc:h2:mem:foo
spring.datasource.username=sa

# spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=100  // 超过100ms抓出来
CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));
INSERT INTO FOO (ID, BAR) VALUES (1, 'AAA');

结果:
在这里插入图片描述

注意事项

(1)没有特殊情况,不要在生产环境打开监控的Servlet
(2)removeAbandoned用于移除被遗弃的连接。ORM和JdbcTemplate不会发生连接泄露,不要开启removeAbandoned。
(3)testXxxx的使用需要注意(testOnBorrow和testOnReturn关闭、testWhileIdle开启 连接有一段时间不用会检查)

# 做检查
spring.datasource.druid.test-on-borrow = false
spring.datasource.druid.test-on-return = false
spring.datasource.druid.test-while-idle = true

(4)务必配置合理的超时时间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/35500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于自适应Sigmoid型函数的内镜图像增强与空间变颜色再现方法

论文题目&#xff1a;Image Enhancement and Space-variant Color Reproduction Method for Endoscopic Images using Adaptive Sigmoid Function 1 摘要 首先&#xff0c;使用YCCBR转换矩阵&#xff0c;将彩色图像分为亮度分量和色度分量&#xff0c;对均匀分布的亮度像素应…

论文笔记:PTR: Prompt Tuning with Rules for Text Classification

Abstract 手动设计大量语言提示麻烦且易出错&#xff0c;而自动生成的提示&#xff0c;在非小样本场景下验证其有效性昂贵且耗时。因此&#xff0c;提示调优以处理多类别分类任务仍然具有挑战。为此&#xff0c;本文提出使用规则进行多类别文本分类提示调优&#xff08;PTR&…

深入理解Java线程间通信

合理的使用Java多线程可以更好地利用服务器资源。一般来讲&#xff0c;线程内部有自己私有的线程上下文&#xff0c;互不干扰。但是当我们需要多个线程之间相互协作的时候&#xff0c;就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。 锁与同步 在…

进程与线程

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

微服务环境搭建

目录 一&#xff0c;微服务介绍 单体架构介绍 垂直应用架构 分布式架构 SOA架构 微服务架构 二&#xff0c;微服务架构简介 三&#xff0c;微服务搭建 模块设计 创建步骤 一&#xff0c;微服务介绍 系统架构大体经历了下面几个过程: 单体应用架构--->垂直应用架构---&…

【python】bin/dec/hex/bnr进制转换函数及fp32转十六进制

我们的目标是┏ (゜ω゜)☞芯片前端全栈工程师~喵&#xff01; 前言 不知道为什么&#xff0c;给脚本专栏选的这个logo有种怪怪的感觉(⊙o⊙)… 为方便后续一些脚本工作&#xff0c;将常用的进制转换函数汇总。所有函数均为字符串输入输出&#xff0c;且不加0x、0b等关键字&a…

计算机硬件和软件

文章目录一 计算机硬件1&#xff09;主板2&#xff09;显示器3&#xff09;键盘4&#xff09;鼠标二 计算机软件&#xff08;一&#xff09;系统软件&#xff08;1&#xff09;操作系统&#xff08;2&#xff09;BIOS&#xff08;3&#xff09;设备驱动程序&#xff08;二&…

K-verse 小型活动来袭!

欢迎来到韩流狂欢节&#xff01; 我们的韩流活动以为期 2 周的 K-verse 小型活动拉开帷幕&#xff01; 在 The Sandbox 的众多合作伙伴中&#xff0c;K League、Anicube、Sandbox Network 和 Cube 娱乐都为此次活动打造了独特的体验。从 11 月 24 日开始&#xff0c;玩家总共可…

digitalLogic_逻辑门和基本公式

文章目录refs逻辑闸或逻辑门与或非门反相器和泡泡与非/或非/异或同或/是/蕴含/蕴含非逻辑函数的表示方法及相互转换逻辑表达式化简基本公式异或和同或的常用运算公式refs digital_logic同或和异或的关系_xuchaoxin1375的博客-CSDN博客_异或与同或的关系模2运算_模二除法和CRC循…

彩虹之眼文化集团整体方案设计

彩虹之眼文化集团开业成功必须克服的问题是&#xff1a;既要有轰动的社会效应&#xff0c;又要有实实在在的营业额。而要使两者兼备&#xff0c;就要求助于开业前期的广告渲染&#xff0c;此方案解决的重要问题就是如何在有轰动社会效应的同时又兼具营业佳绩。因此本次公司宣传…

Android8.1 MTK 浏览器下载的apk点击无反应不能安装

最近测试人员发现用原生浏览器下载的apk点击安装时无反应&#xff0c;不能安装。 在/vendor/mediatek/proprietary/packages/apps/Browser/src/com/android/browser/DownloadHandler.java 中&#xff0c;发现下载的apk文件缺少了mime类型&#xff0c;如下图 mimetype null造…

第五章TCP/IP 网络在我们身边

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

Ubuntu22.2下C语言编程实现,首次,最佳适应算法

参考目录&#xff1a;1.题目要求2.分析设计3.程序代码4.运行截图5.程序说明1.题目要求 编写C语言程序&#xff0c;模拟实现首次/最佳/最坏适应算法(选择其中之一即可)的内存块分配和回收&#xff0c;要求每次分配和回收后显示出空闲分区和已分配分区的情况。假设初始状态下&am…

SpringBoot整合redis+mysql

SpringBoot整合Redis 测试连接 添加相关依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation&qu…

【TFS-CLUB社区 第7期赠书活动】〖从零开始利用Excel与Python进行数据分析 自动化办公实战宝典〗等你来拿,参与评论,即可有机获得

文章目录❤️‍&#x1f525; 赠书活动 - 《从零开始利用Excel与Python进行数据分析 自动化办公实战宝典》❤️‍&#x1f525; 编辑推荐❤️‍&#x1f525; 抽奖方式与截止时间❤️‍&#x1f525; 赠书活动 → 获奖名单❤️‍&#x1f525; 赠书活动 - 《从零开始利用Excel与…

【数据结构】二叉树的前中后序遍历

二叉树的三种遍历1. 创建一棵简单的二叉树1.1 二叉树结构体实现1.2 创造一个二叉树结点的函数1.3 手动创造一棵二叉树2.为什么要遍历&#xff1f;3.最重要的知识&#xff1a;由二叉树引出的子问题分析4.遍历4.1 前序遍历4.2 中序遍历4.3 后序遍历5.总结1. 创建一棵简单的二叉树…

基于springboot车辆充电桩设计与实现的源码+文档

摘 要 随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;车辆充电桩管理系统也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&…

18.3 内存池概念、代码实现和详细分析

一&#xff1a;内存池的概念和实现原理概述 malloc&#xff1a;内存浪费&#xff0c;频繁分配小块内存&#xff0c;浪费更加明显。 “内存池”要解决什么问题&#xff1f; 1、减少malloc()的次数&#xff0c;减少malloc()调用次数就意味着减少对内存的浪费 2、减少malloc()的…

JavaEE高阶---SpringBoot的创建和使用

一 : 什么是SpringBoot? Spring的诞生是为了简化 Java 程序的开发的,Spring Boot 的诞生是为了简化 Spring 程序开发的.Spring Boot 是所有基于 Spring 开发的项目的起点 . Spring Boot 的设计是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件 . Sprin…

深度学习系列2——Pytorch 图像分类(AlexNet)

1. 概述 本文主要是参照 B 站 UP 主 霹雳吧啦Wz 的视频学习笔记&#xff0c;参考的相关资料在文末参照栏给出&#xff0c;包括实现代码和文中用的一些图片。 整个工程已经上传个人的 github https://github.com/lovewinds13/QYQXDeepLearning &#xff0c;下载即可直接测试&a…