Spring Boot数据库开发
通常SpringBoot数据库开发,会引入spring-boot-starter-jdbc
,而如果引入了spring-boot-starter-jdbc
,但没有可用的数据源或者没有配置,那么在运行Spring Boot时会出现异常,因为spring-boot-starter-jdbc
内部会自动配置一个数据源,如果不能连接就会引发异常
假如系统用的数据源来自MySQL,但还没有配置,可以在POM里引入一个内存数据库(也可以配置成持久化存储在硬盘上),轻量级的关系型数据库管理系统,如下所示
<!-- 引入H2数据库的依赖 --><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency>
然后在启动SpringBoot,访问如下地址就能看到H2的访问页面,但实际上这个数据库并没有单独安装
在启动日志里也能看到这个数据库的启动
也可以取消SpringBoot默认数据源的创建,如下代码所示
package com.sbdev;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@RestController
//exclude = DataSourceAutoConfiguration.class 禁止自动配置数据源
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SbDevApplication {@GetMapping("/test")public Map<String, String> test() {Map<String, String> map = new HashMap<>();map.put("success", "true");map.put("message", "我的第一个Spring Boot程序");return map;}public static void main(String[] args) {SpringApplication.run(SbDevApplication.class, args);}}
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
表示启动SpringBoot,不再使用DataSourceAutoConfiguration
这个配置类,如果没有配置数据源,运行时就不再发生异常,这也变相的说明SpringBoot的配置理念,使用配置类来生成对应的Bean,而Bean的属性可以通过配置文件自定义
正常情况在开发应用的时候都会提前配置好,需要确认自己的环境,如下列情况不同的数据库版本配置对应的数据库驱动及参数
服务启动过程中经常会遇到的一个问题是无法创建连接,其中有一种原因是数据库的版本和连接驱动类的版本不匹配,如果使用的是MySQL8.0以下的版本,那在POM中添加的依赖应该是
<!-- 引入MySQL数据库连接驱动依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.29</version></dependency>
配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
而如果MySQL用的是8.0以上的版本,那么POM中应该添加的依赖是
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version> <!-- 使用实际的版本号 --></dependency>
或者
<!-- 引入MySQL连接器,用于在运行时连接MySQL数据库 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency>
配置数据源的时候,driverClassName配置应该是props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
配置好这些之后,在SpringBoot的配置文件application.yml
中,写入数据源配置,如下所示
spring.application.name:- SBDevspring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: Ms123!@#server:servlet:context-path: /SBDevport: 8001
这样POM中既不需要引入h2database
也不需要在启动类上添加exclude = DataSourceAutoConfiguration.class
,如果上述数据源配置无误且能正常链接,则启动SpringBoot的时候就可以正常启动
这里的配置最终会被读取到DataSourceAutoConfiguration
中,然后再去创建数据源,看一下这个类的源码如下
/*** 自动配置数据源的类。根据应用程序的条件自动配置嵌入式数据库或连接池。* * @AutoConfiguration before = {SqlInitializationAutoConfiguration.class} 指定此配置类在SqlInitializationAutoConfiguration之前加载* @ConditionalOnClass {DataSource.class, EmbeddedDatabaseType.class} 检查类路径中是否存在DataSource和EmbeddedDatabaseType类,以决定是否应该加载此配置* @ConditionalOnMissingBean type = {"io.r2dbc.spi.ConnectionFactory"} 如果应用程序中不存在ConnectionFactory bean,则加载此配置* @EnableConfigurationProperties {DataSourceProperties.class} 启用对DataSourceProperties属性的配置* @Import {DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class} 导入额外的配置类*/
@AutoConfiguration(before = {SqlInitializationAutoConfiguration.class}
)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(type = {"io.r2dbc.spi.ConnectionFactory"}
)
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceCheckpointRestoreConfiguration.class})
public class DataSourceAutoConfiguration {/*** 判断是否应该配置嵌入式数据库的条件类。* 它检查是否设置了spring.datasource.url属性,或者是否存在支持的连接池数据源。*/static class EmbeddedDatabaseCondition extends SpringBootCondition {private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";private final SpringBootCondition pooledCondition = new PooledDataSourceCondition();EmbeddedDatabaseCondition() {}/*** 根据当前环境和条件判断是否应该配置嵌入式数据库。* * @param context 条件上下文,提供环境和类加载器等信息* @param metadata 注解类型元数据,用于获取注解参数等信息* @return 条件匹配的结果*/public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage.Builder message = ConditionMessage.forCondition("EmbeddedDataSource", new Object[0]);if (this.hasDataSourceUrlProperty(context)) {return ConditionOutcome.noMatch(message.because("spring.datasource.url is set"));} else if (this.anyMatches(context, metadata, new Condition[]{this.pooledCondition})) {return ConditionOutcome.noMatch(message.foundExactly("supported pooled data source"));} else {EmbeddedDatabaseType type = EmbeddedDatabaseConnection.get(context.getClassLoader()).getType();return type == null ? ConditionOutcome.noMatch(message.didNotFind("embedded database").atAll()) : ConditionOutcome.match(message.found("embedded database").items(new Object[]{type}));}}/*** 检查是否设置了spring.datasource.url属性并且该属性有文本值。* * @param context 条件上下文* @return 如果设置了有效的spring.datasource.url属性,则返回true;否则返回false*/private boolean hasDataSourceUrlProperty(ConditionContext context) {Environment environment = context.getEnvironment();if (environment.containsProperty("spring.datasource.url")) {try {return StringUtils.hasText(environment.getProperty("spring.datasource.url"));} catch (IllegalArgumentException var4) {}}return false;}}/*** 判断是否应该配置连接池数据源的条件类。*/static class PooledDataSourceAvailableCondition extends SpringBootCondition {PooledDataSourceAvailableCondition() {}/*** 根据当前环境判断是否应该配置连接池数据源。* * @param context 条件上下文* @param metadata 注解类型元数据* @return 条件匹配的结果*/public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage.Builder message = ConditionMessage.forCondition("PooledDataSource", new Object[0]);return DataSourceBuilder.findType(context.getClassLoader()) != null ? ConditionOutcome.match(message.foundExactly("supported DataSource")) : ConditionOutcome.noMatch(message.didNotFind("supported DataSource").atAll());}}/*** 管理连接池数据源配置的条件类。* 根据是否存在特定的DataSource类型或属性来决定是否应用配置。*/static class PooledDataSourceCondition extends AnyNestedCondition {PooledDataSourceCondition() {super(ConfigurationPhase.PARSE_CONFIGURATION);}/*** 当存在支持的连接池数据源类型时触发的条件。*/@Conditional({PooledDataSourceAvailableCondition.class})static class PooledDataSourceAvailable {PooledDataSourceAvailable() {}}/*** 当spring.datasource.type属性被设置时触发的条件。*/@ConditionalOnProperty(prefix = "spring.datasource",name = {"type"})static class ExplicitType {ExplicitType() {}}}/*** 配置连接池数据源的配置类。* 在不存在DataSource和XADataSource bean且满足其他条件的情况下激活。*/@Configuration(proxyBeanMethods = false)@Conditional({PooledDataSourceCondition.class})@ConditionalOnMissingBean({DataSource.class, XADataSource.class})@Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})protected static class PooledDataSourceConfiguration {protected PooledDataSourceConfiguration() {}/*** 提供基于DataSourceProperties配置的JdbcConnectionDetails bean。* * @param properties DataSource的属性* @return JdbcConnectionDetails的实例*/@Bean@ConditionalOnMissingBean({JdbcConnectionDetails.class})PropertiesJdbcConnectionDetails jdbcConnectionDetails(DataSourceProperties properties) {return new PropertiesJdbcConnectionDetails(properties);}}/*** 配置嵌入式数据库的配置类。* 在不存在DataSource和XADataSource bean且满足其他条件的情况下激活。*/@Configuration(proxyBeanMethods = false)@Conditional({EmbeddedDatabaseCondition.class})@ConditionalOnMissingBean({DataSource.class, XADataSource.class})@Import({EmbeddedDataSourceConfiguration.class})protected static class EmbeddedDatabaseConfiguration {protected EmbeddedDatabaseConfiguration() {}}
}
代码中不难看出加载了配置类@EnableConfigurationProperties({DataSourceProperties.class})
, 这是Spring暴露的配置,在看一下这个配置类的源码如下
/*** 数据源属性配置类,用于封装数据源相关的配置信息。* 通过@ConfigurationProperties注解,绑定到配置文件中以spring.datasource为前缀的配置项。*/
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {private ClassLoader classLoader;private boolean generateUniqueName = true;private String name;private Class<? extends DataSource> type;private String driverClassName;private String url;private String username;private String password;private String jndiName;private EmbeddedDatabaseConnection embeddedDatabaseConnection;private Xa xa = new Xa();private String uniqueName;/*** 默认构造函数*/public DataSourceProperties() {}/*** 实现BeanClassLoaderAware接口,设置类加载器* @param classLoader 类加载器*/public void setBeanClassLoader(ClassLoader classLoader) {this.classLoader = classLoader;}/*** 实现InitializingBean接口,初始化数据源构建器* @throws Exception 初始化异常*/public void afterPropertiesSet() throws Exception {if (this.embeddedDatabaseConnection == null) {this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);}}/*** 初始化DataSourceBuilder,用于构建DataSource实例。* @return DataSourceBuilder实例*/public DataSourceBuilder<?> initializeDataSourceBuilder() {return DataSourceBuilder.create(this.getClassLoader()).type(this.getType()).driverClassName(this.determineDriverClassName()).url(this.determineUrl()).username(this.determineUsername()).password(this.determinePassword());}/*** 获取是否生成唯一名称的属性。* @return true表示生成唯一名称,false表示不生成*/public boolean isGenerateUniqueName() {return this.generateUniqueName;}/*** 设置是否生成唯一名称的属性。* @param generateUniqueName true表示生成唯一名称,false表示不生成*/public void setGenerateUniqueName(boolean generateUniqueName) {this.generateUniqueName = generateUniqueName;}/*** 获取数据源名称。* @return 数据源名称*/public String getName() {return this.name;}/*** 设置数据源名称。* @param name 数据源名称*/public void setName(String name) {this.name = name;}/*** 获取数据源类型。* @return 数据源类型*/public Class<? extends DataSource> getType() {return this.type;}/*** 设置数据源类型。* @param type 数据源类型*/public void setType(Class<? extends DataSource> type) {this.type = type;}/*** 获取驱动类名称。* @return 驱动类名称*/public String getDriverClassName() {return this.driverClassName;}/*** 设置驱动类名称。* @param driverClassName 驱动类名称*/public void setDriverClassName(String driverClassName) {this.driverClassName = driverClassName;}/*** 确定驱动类名称。* 如果已设置驱动类名称,则直接返回;否则根据URL确定驱动类名称。* @return 驱动类名称* @throws DataSourceBeanCreationException 如果无法确定驱动类名称,则抛出异常*/public String determineDriverClassName() {if (StringUtils.hasText(this.driverClassName)) {Assert.state(this.driverClassIsLoadable(), () -> {return "Cannot load driver class: " + this.driverClassName;});return this.driverClassName;} else {String driverClassName = null;if (StringUtils.hasText(this.url)) {driverClassName = DatabaseDriver.fromJdbcUrl(this.url).getDriverClassName();}if (!StringUtils.hasText(driverClassName)) {driverClassName = this.embeddedDatabaseConnection.getDriverClassName();}if (!StringUtils.hasText(driverClassName)) {throw new DataSourceBeanCreationException("Failed to determine a suitable driver class", this, this.embeddedDatabaseConnection);} else {return driverClassName;}}}/*** 检查驱动类是否可加载。* @return true表示驱动类可加载,false表示不可加载*/private boolean driverClassIsLoadable() {try {ClassUtils.forName(this.driverClassName, (ClassLoader)null);return true;} catch (UnsupportedClassVersionError var2) {throw var2;} catch (Throwable var3) {var3.printStackTrace();return false;}}/*** 获取URL。* @return JDBC URL*/public String getUrl() {return this.url;}/*** 设置URL。* @param url JDBC URL*/public void setUrl(String url) {this.url = url;}/*** 确定URL。* 如果已设置URL,则直接返回;否则根据数据库名称确定URL。* @return JDBC URL* @throws DataSourceBeanCreationException 如果无法确定URL,则抛出异常*/public String determineUrl() {if (StringUtils.hasText(this.url)) {return this.url;} else {String databaseName = this.determineDatabaseName();String url = databaseName != null ? this.embeddedDatabaseConnection.getUrl(databaseName) : null;if (!StringUtils.hasText(url)) {throw new DataSourceBeanCreationException("Failed to determine suitable jdbc url", this, this.embeddedDatabaseConnection);} else {return url;}}}/*** 确定数据库名称。* 如果需要生成唯一名称,则生成并返回唯一名称;否则根据name属性返回名称。* @return 数据库名称*/public String determineDatabaseName() {if (this.generateUniqueName) {if (this.uniqueName == null) {this.uniqueName = UUID.randomUUID().toString();}return this.uniqueName;} else if (StringUtils.hasLength(this.name)) {return this.name;} else {return this.embeddedDatabaseConnection != EmbeddedDatabaseConnection.NONE ? "testdb" : null;}}/*** 获取用户名。* @return 用户名*/public String getUsername() {return this.username;}/*** 设置用户名。* @param username 用户名*/public void setUsername(String username) {this.username = username;}/*** 确定用户名。* 如果已设置用户名,则直接返回;否则对于嵌入式数据库,返回"sa"。* @return 用户名*/public String determineUsername() {if (StringUtils.hasText(this.username)) {return this.username;} else {return EmbeddedDatabaseConnection.isEmbedded(this.determineDriverClassName(), this.determineUrl()) ? "sa" : null;}}/*** 获取密码。* @return 密码*/public String getPassword() {return this.password;}/*** 设置密码。* @param password 密码*/public void setPassword(String password) {this.password = password;}/*** 确定密码。* 如果已设置密码,则直接返回;否则对于嵌入式数据库,返回空字符串。* @return 密码*/public String determinePassword() {if (StringUtils.hasText(this.password)) {return this.password;} else {return EmbeddedDatabaseConnection.isEmbedded(this.determineDriverClassName(), this.determineUrl()) ? "" : null;}}/*** 获取JNDI名称。* @return JNDI名称*/public String getJndiName() {return this.jndiName;}/*** 设置JNDI名称。* @param jndiName JNDI名称*/public void setJndiName(String jndiName) {this.jndiName = jndiName;}/*** 获取嵌入式数据库连接类型。* @return 嵌入式数据库连接类型*/public EmbeddedDatabaseConnection getEmbeddedDatabaseConnection() {return this.embeddedDatabaseConnection;}/*** 设置嵌入式数据库连接类型。* @param embeddedDatabaseConnection 嵌入式数据库连接类型*/public void setEmbeddedDatabaseConnection(EmbeddedDatabaseConnection embeddedDatabaseConnection) {this.embeddedDatabaseConnection = embeddedDatabaseConnection;}/*** 获取类加载器。* @return 类加载器*/public ClassLoader getClassLoader() {return this.classLoader;}/*** 获取Xa配置。* @return Xa配置*/public Xa getXa() {return this.xa;}/*** 设置Xa配置。* @param xa Xa配置*/public void setXa(Xa xa) {this.xa = xa;}/*** Xa配置类,用于存储XA数据源的相关配置。*/public static class Xa {private String dataSourceClassName;private Map<String, String> properties = new LinkedHashMap();/*** 获取数据源类名称。* @return 数据源类名称*/public String getDataSourceClassName() {return this.dataSourceClassName;}/*** 设置数据源类名称。* @param dataSourceClassName 数据源类名称*/public void setDataSourceClassName(String dataSourceClassName) {this.dataSourceClassName = dataSourceClassName;}/*** 获取属性配置。* @return 属性配置*/public Map<String, String> getProperties() {return this.properties;}/*** 设置属性配置。* @param properties 属性配置*/public void setProperties(Map<String, String> properties) {this.properties = properties;}}/*** 数据源创建异常类,用于处理数据源创建过程中的异常。*/static class DataSourceBeanCreationException extends BeanCreationException {private final DataSourceProperties properties;private final EmbeddedDatabaseConnection connection;/*** 构造函数。* @param message 异常信息* @param properties 数据源属性* @param connection 嵌入式数据库连接类型*/DataSourceBeanCreationException(String message, DataSourceProperties properties, EmbeddedDatabaseConnection connection) {super(message);this.properties = properties;this.connection = connection;}/*** 获取数据源属性。* @return 数据源属性*/DataSourceProperties getProperties() {return this.properties;}/*** 获取嵌入式数据库连接类型。* @return 嵌入式数据库连接类型*/EmbeddedDatabaseConnection getConnection() {return this.connection;}}
}
代码中不难看出,要求对应的配置项以spring.datasource
开头,上一段源码中有一段加入各种数据源配置的地方
@Import({DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class})
再深入看一下源码,以第一个数据源配置类为例
/*** 数据源配置类,用于根据不同的数据源类型创建对应的数据源实例。*/
abstract class DataSourceConfiguration {/*** 根据连接详情和数据源类型创建数据源实例。* * @param connectionDetails 连接详情,包含 JDBC URL、驱动类名、用户名和密码等信息。* @param type 数据源的类类型。* @param classLoader 类加载器。* @param <T> 数据源类型。* @return 创建的数据源实例。*/private static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type, ClassLoader classLoader) {// 使用 DataSourceBuilder 创建并配置数据源实例,然后返回。return DataSourceBuilder.create(classLoader).type(type).driverClassName(connectionDetails.getDriverClassName()).url(connectionDetails.getJdbcUrl()).username(connectionDetails.getUsername()).password(connectionDetails.getPassword()).build();}/*** 配置通用的数据源 bean,当不存在自定义数据源 bean 且配置了 spring.datasource.type 时使用。*/@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean({DataSource.class})@ConditionalOnProperty(name = {"spring.datasource.type"})static class Generic {/*** 根据连接属性和连接详情创建并配置数据源 bean。* * @param properties 数据源属性。* @param connectionDetails 连接详情。* @return 配置后的数据源实例。*/@BeanDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {// 调用 createDataSource 方法创建并返回数据源实例。return (DataSource)DataSourceConfiguration.createDataSource(connectionDetails, properties.getType(), properties.getClassLoader());}}/*** 配置 Oracle UCP 数据源 bean,当类路径下存在 PoolDataSourceImpl 类且未定义 DataSource bean 时使用。*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({PoolDataSourceImpl.class, OracleConnection.class})@ConditionalOnMissingBean({DataSource.class})@ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "oracle.ucp.jdbc.PoolDataSource",matchIfMissing = true)static class OracleUcp {/*** 创建并配置 Oracle UCP 数据源 bean。* * @param properties 数据源属性。* @param connectionDetails 连接详情。* @return 配置后的 Oracle UCP 数据源实例。* @throws SQLException 如果配置数据源时发生错误。*/@Bean@ConfigurationProperties(prefix = "spring.datasource.oracleucp")PoolDataSourceImpl dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) throws SQLException {PoolDataSourceImpl dataSource = (PoolDataSourceImpl)DataSourceConfiguration.createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader());// 如果设置了数据源名称,则应用到 Oracle UCP 数据源。if (StringUtils.hasText(properties.getName())) {dataSource.setConnectionPoolName(properties.getName());}return dataSource;}}/*** 配置 DBCP2 数据源 bean,当类路径下存在 BasicDataSource 类且未定义 DataSource bean 时使用。*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({BasicDataSource.class})@ConditionalOnMissingBean({DataSource.class})@ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "org.apache.commons.dbcp2.BasicDataSource",matchIfMissing = true)static class Dbcp2 {/*** 创建并配置 DBCP2 数据源 bean。* * @param properties 数据源属性。* @param connectionDetails 连接详情。* @return 配置后的 DBCP2 数据源实例。*/@Bean@ConfigurationProperties(prefix = "spring.datasource.dbcp2")BasicDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {Class<? extends DataSource> dataSourceType = BasicDataSource.class;return (BasicDataSource)DataSourceConfiguration.createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());}}/*** 配置 HikariCP 数据源 bean,当类路径下存在 HikariDataSource 类且未定义 DataSource bean 时使用。*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({HikariDataSource.class})@ConditionalOnMissingBean({DataSource.class})@ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "com.zaxxer.hikari.HikariDataSource",matchIfMissing = true)static class Hikari {/*** 创建并配置 HikariCP 数据源 bean。* * @param properties 数据源属性。* @param connectionDetails 连接详情。* @return 配置后的 HikariCP 数据源实例。*/@Bean@ConfigurationProperties(prefix = "spring.datasource.hikari")HikariDataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(connectionDetails, HikariDataSource.class, properties.getClassLoader());// 如果设置了数据源名称,则应用到 HikariCP 数据源。if (StringUtils.hasText(properties.getName())) {dataSource.setPoolName(properties.getName());}return dataSource;}}/*** 配置 Tomcat 数据源 bean,当类路径下存在 org.apache.tomcat.jdbc.pool.DataSource 类且未定义 DataSource bean 时使用。*/@Configuration(proxyBeanMethods = false)@ConditionalOnClass({org.apache.tomcat.jdbc.pool.DataSource.class})@ConditionalOnMissingBean({DataSource.class})@ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "org.apache.tomcat.jdbc.pool.DataSource",matchIfMissing = true)static class Tomcat {/*** 创建并配置 Tomcat 数据源 bean。* * @param properties 数据源属性。* @param connectionDetails 连接详情。* @return 配置后的 Tomcat 数据源实例。*/@Bean@ConfigurationProperties(prefix = "spring.datasource.tomcat")org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties, JdbcConnectionDetails connectionDetails) {Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;org.apache.tomcat.jdbc.pool.DataSource dataSource = (org.apache.tomcat.jdbc.pool.DataSource)DataSourceConfiguration.createDataSource(connectionDetails, dataSourceType, properties.getClassLoader());DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(connectionDetails.getJdbcUrl());String validationQuery = databaseDriver.getValidationQuery();// 如果存在验证查询语句,则启用borrow时的验证并设置验证查询语句。if (validationQuery != null) {dataSource.setTestOnBorrow(true);dataSource.setValidationQuery(validationQuery);}return dataSource;}}
}
如此Spring Boot就可以通过我们的配置,将数据源创建出来了,其他的也是同理,比如Spring MVC、Redis等等,近一步测试一下,先写一个控制器, 代码如下
package com.sbdev.controller.db;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.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;@RestController
@RequestMapping("/role")
public class DataSourceController {// 注入JdbcTemplate,它由Spring Boot自动创建不需要我们干预@Autowiredprivate JdbcTemplate jdbcTemplate = null;/*** 获取角色* @param id 角色编号* @return 角色信息*/@GetMapping("/info/{id}")public Map<String, Object> getRole(@PathVariable("id") Long id) {System.out.println("DataSource类型:" + jdbcTemplate.getDataSource().getClass().getName());Map<String, Object> roleMap = null;String sql = "select id, role_name, note from t_role where id = ?";roleMap = jdbcTemplate.queryForMap(sql, id);return roleMap;}
}
然后访问地址http://localhost:8001/SBDev/role/info/2
即可
Spring Boot默认使用Hikari数据源,有时候我们想切换不同的数据源,Spring Boot也给予了支持,例如另一个数据源是DBCP2,先在POM添加该依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-dbcp2</artifactId><version>2.12.0</version></dependency>
接着修改application.yml文件数据库的配置,如下所示
# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: - SBDevspring:# 数据源配置,用于连接和管理数据库datasource:# 数据库驱动类名,指定连接MySQL数据库所需的驱动类driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false# 数据库用户名,用于身份验证和授权username: root# 数据库密码,与用户名配合用于身份验证和授权password: Ms123!@## 数据源类型,使用Apache Commons DBCP2提供的基本数据源type: org.apache.commons.dbcp2.BasicDataSource# DBCP2数据源特定配置,用于管理连接池的大小和行为dbcp2:# 最大空闲连接数,超过该数目的空闲连接将被关闭max-idle: 20# 等待连接池分配连接的最大时间,超过该时间仍未获取到连接将抛出异常max-wait-millis: 5000# 连接池允许的最大连接数,超过该数目将无法获取新的连接max-total: 50# 最小空闲连接数,连接池会维护至少这么多的空闲连接# 服务器配置,用于设置服务器端口和上下文路径
server:servlet:# 应用程序上下文路径,用于区分不同的Spring Boot应用或服务context-path: /SBDev# 服务器端口,指定应用监听的端口号port: 8001
Spring Boot整合MyBatis
在Spring Boot中整合MyBatis和传统方式差不多,只是部分MyBatis组件可以在SpringBoot配置文件中进行配置,且常见的类都会自动初始化,不需要编写,
添加依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency>
建表
use ssm;Create Table t_user(id int(12) not null auto_increment comment '主键',username varchar(255) not null comment '用户名',password varchar(255) not null comment '密码',sex int(3) not null default 0 comment '性别',note varchar(255) comment '备注',primary key(id),CHECK (sex in (0,1))
);insert into t_user(username,password,sex,note) values('admin','123456',0,'管理员');
insert into t_user(username,password,sex,note) values('user','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user2','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user3','123456',1,'普通用户');
insert into t_user(username,password,sex,note) values('user4','123456',1,'普通用户');
写POJO
package com.sbdev.dic;public enum SexEnum {MALE(0, "男"),FEMALE(1, "女");private Integer id;private String value;SexEnum(Integer id, String value) {this.id = id;this.value = value;}/*** 获取根据编号获取性别枚举* @param id 编号* @return 枚举*/public static SexEnum getSexEnum(Integer id) {for (SexEnum sex : SexEnum.values()) {if (sex.getId().equals(id)) {return sex;}}return null;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}
}
package com.sbdev.pojo;import com.sbdev.dic.SexEnum;
import org.apache.ibatis.type.Alias;import java.io.Serializable;@Alias("user")
public class User implements Serializable {private static final long serialVersionUID = 2386785787854557L;private Long id;private String userName;private SexEnum sex;private String note;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUserName() {return userName;}public void setUserName(String userName) {this.userName = userName;}public SexEnum getSex() {return sex;}public void setSex(SexEnum sex) {this.sex = sex;}public String getNote() {return note;}public void setNote(String note) {this.note = note;}
}
写映射文件
<?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.learn.ssm.chapter25.dao.UserDao"><select id="getUser" parameterType="long" resultType="user">select id, user_name as userName, sex, note from t_user where id = #{id}</select><insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">insert into t_user(user_name, sex, note) values (#{userName}, #{sex}, #{note})</insert>
</mapper>
定义DAO接口层
package com.sbdev.dao;import com.sbdev.pojo.User;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserDao {public User getUser(Long id);public Integer insertUser(User user);
}
编写TypeHandler
因为用户的性别是个枚举,为了更方便的使用它,需要编写一个TypeHandler
package com.sbdev.type.handler;import com.sbdev.dic.SexEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** 性别枚举类型处理器,用于MyBatis中将数据库中的整型数据与SexEnum枚举类型相互转换。* 使用@MappedJdbcTypes和@MappedTypes注解分别指定了对应的JDBC类型和Java类型。*/
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes({SexEnum.class})
public class SexTypeHandler extends BaseTypeHandler<SexEnum> {/*** 设置非空参数。* 将枚举类型SexEnum的id值设置到PreparedStatement中,对应数据库中的整型字段。* @param ps PreparedStatement对象* @param idx 参数索引* @param sex 性别枚举值* @param jdbcType JDBC类型* @throws SQLException 如果设置参数时发生错误*/@Overridepublic void setNonNullParameter(PreparedStatement ps, int idx, SexEnum sex, JdbcType jdbcType) throws SQLException {ps.setInt(idx, sex.getId());}/*** 从ResultSet中获取非空结果。* 根据列名获取整型值,并将其转换为SexEnum枚举类型。* @param rs ResultSet对象* @param columnName 列名* @return 性别枚举值* @throws SQLException 如果获取结果时发生错误*/@Overridepublic SexEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {Integer id = rs.getInt(columnName);return SexEnum.getSexEnum(id);}/*** 从ResultSet中获取非空结果。* 根据索引获取整型值,并将其转换为SexEnum枚举类型。* @param rs ResultSet对象* @param idx 列索引* @return 性别枚举值* @throws SQLException 如果获取结果时发生错误*/@Overridepublic SexEnum getNullableResult(ResultSet rs, int idx) throws SQLException {Integer id = rs.getInt(idx);return SexEnum.getSexEnum(id);}/*** 从CallableStatement中获取非空结果。* 根据索引获取整型值,并将其转换为SexEnum枚举类型。* @param cs CallableStatement对象* @param idx 列索引* @return 性别枚举值* @throws SQLException 如果获取结果时发生错误*/@Overridepublic SexEnum getNullableResult(CallableStatement cs, int idx) throws SQLException {Integer id = cs.getInt(idx);return SexEnum.getSexEnum(id);}
}
配置MyBatis
在传统的MyBatis使用中,我们还需要XML配置文件或者使用Java代码继承配置类进行配置,在SpringBoot中,通过application.yml进行配置即可
# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDevspring:# 数据源配置,用于连接和管理数据库datasource:# 数据库驱动类名,指定连接MySQL数据库所需的驱动类driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false# 数据库用户名,用于身份验证和授权username: root# 数据库密码,与用户名配合用于身份验证password: Ms123!@## 数据源类型,使用Apache Commons DBCP2提供的基本数据源type: org.apache.commons.dbcp2.BasicDataSource# DBCP2特定的配置,用于管理连接池的大小和行为dbcp2:# 最大空闲连接数,超过该数目的空闲连接将被关闭max-idle: 20# 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常max-wait-millis: 5000# 连接池允许的最大连接数,超过该数目将拒绝新的连接请求max-total: 50# 服务器配置,用于设置应用的访问路径和端口
server:# 应用程序的上下文路径,用于区分不同的服务或模块servlet:context-path: /SBDev# 服务器端口,指定应用监听的网络端口号port: 8001# mybatis配置项
mybatis:# 映射文件路径mapper-locations: classpath:mapper/*.xml# TypeHandler扫描包type-handlers-package: com.sbdev.type.handler# 扫描别名type-aliases-package: com.sbdev.pojologging:level:# 日志级别root: DEBUG
开发业务层
package com.sbdev.service;import com.sbdev.pojo.User;public interface UserService {public User getUser(Long id);public Integer insertUser(User user);}
package com.sbdev.service.impl;import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao = null;@Override@Cacheable(value="redisCache", key="'redis_user_'+#id")public User getUser(Long id) {return userDao.getUser(id);}@Override// 事务管理器由Spring Boot自动装配,无需自己配置@Transactional(isolation = Isolation.READ_COMMITTED)public Integer insertUser(User user) {return userDao.insertUser(user);}
}
开发控制器
package com.sbdev.controller;import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController // REST风格网站
@RequestMapping("/user")
public class UserController {// 用户服务接口@Autowiredprivate UserService userService = null;/*** 获取用户信息* @param id 用户编号* @return 用户信息*/@GetMapping("/info/{id}")public User getUser(@PathVariable("id") Long id) {User user = userService.getUser(id);return user;}@GetMapping("/addition/{id}")public User insertUser(@PathVariable("id") Long id) {User user = new User();user.setSex(SexEnum.getSexEnum(id.intValue()%2));user.setUserName("user_name_" + id);user.setNote("note_" + id);userService.insertUser(user);return user;}@GetMapping("/print/{user}")public User print(User user) {return user;}
}
配置SpringBoot启动
/*** 应用程序的入口点。* 使用@SpringBootApplication注解标记这个类作为Spring Boot应用程序的起点。* scanBasePackages属性指定了Spring应用程序上下文应该扫描的包,即在这个包及其子包下寻找组件(如控制器、服务、配置类等)。* 这里指定的包是"com.sbdev",意味着所有在这个包下面的组件都会被Spring Boot自动识别和管理。*/
@SpringBootApplication(scanBasePackages = "com.sbdev"
)
/*** 配置MyBatis的Mapper扫描器。* 通过此注解,Spring Boot将自动扫描指定包下的所有Mapper接口,并将其注册到MyBatis的SqlSessionFactory或SqlSessionTemplate中。* @param basePackages 指定需要扫描的Mapper接口所在的包。Spring Boot会递归扫描指定包及其子包下的所有接口。* @param annotationClass 指定需要扫描的接口上应存在的注解。这里指定为@Mapper,意味着只有标注了@Mapper注解的接口才会被扫描和处理。* @param sqlSessionFactoryRef 指定SqlSessionFactory的Bean名称。扫描到的Mapper接口将使用这个SqlSessionFactory来创建SqlSession。* @param sqlSessionTemplateRef 指定SqlSessionTemplate的Bean名称。扫描到的Mapper接口将使用这个SqlSessionTemplate来执行SQL操作。*/
@MapperScan(basePackages = "com.sbdev",annotationClass = Mapper.class,sqlSessionFactoryRef = "sqlSessionFactory",sqlSessionTemplateRef = "sqlSessionTemplate"
)@EnableCaching
public class SbDevApplication {public static void main(String[] args) {SpringApplication.run(SbDevApplication.class, args);}
SpringBoot启动类配置,注释写的比较清楚
注意这个配置
sqlSessionFactoryRef = "sqlSessionFactory",sqlSessionTemplateRef = "sqlSessionTemplate"
,在这里可以删除它们,因为在项目中不存在多个SqlSessionFactory
和SqlSessionTemplate
,但是从头到尾都没有sqlSessionFactory
和sqlSessionTemplate
相关的配置或定义,那在这里却可以直接配置的原因便是当我们配置了数据源javax.sql.DataSource
后,Spring Boot就会自动创建数据源的Bean,并且将它装配到Spring IoC容器中,而这个过程无需开发,也是Spring Boot的特色
插件
package com.sbdev.plugin;import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;import java.util.Properties;
/*** 消费时间插件,用于拦截MyBatis的Executor执行查询操作,计算查询操作的消耗时间。* 该插件通过实现Interceptor接口,利用MyBatis的插件机制,在查询执行前后插入计时逻辑。*/
@Intercepts(@Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class ConsumptionTimePlugin implements Interceptor {/*** 对Executor的query方法进行拦截。* 在执行查询操作前后记录时间,以计算查询操作的耗时,并输出到控制台。** @param invocation 查询操作的调用信息,包含执行查询所需的所有参数。* @return 查询操作的结果对象。* @throws Throwable 查询操作中可能抛出的任何异常。*/@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 记录查询开始时间long start = System.currentTimeMillis();// 执行查询操作Object returnObj = invocation.proceed();// 记录查询结束时间long end = System.currentTimeMillis();// 计算并输出查询耗时System.out.println("耗时【" + (end - start)+"】毫秒");return returnObj;}/*** 为指定的目标对象创建一个插件代理。* 该方法用于实现插件的包装逻辑,将当前插件应用于目标对象,以拦截目标对象的方法调用。** @param target 被插件化的对象,即MyBatis的Executor实例。* @return 包装了当前插件的目标对象的代理实例。*/@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}/*** 设置插件的属性。* 该方法用于接收从配置文件中读取的插件属性,当前插件未使用任何属性,因此该方法为空实现。** @param properties 插件的属性配置。*/@Overridepublic void setProperties(Properties properties) {// 该插件不使用任何属性,因此该方法为空实现}
}
这是一个MyBatis插件,可以用来监控执行查询SQL消耗的时间,因为插件相对独立,可以将其配置到MyBatis自身的配置文件中,在resources
目录下创建mybatis-config.xml
,来配置插件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 配置文件的根元素,用于定义MyBatis的全局属性、类型别名、映射器等 -->
<configuration><!-- 插件配置,用于注册自定义的插件 --><plugins><!-- 注册一个插件,该插件实现了com.sbdev.plugin.ConsumptionTimePlugin接口 --><!-- 该插件用于记录方法的执行时间,实现性能监控 --><plugin interceptor="com.sbdev.plugin.ConsumptionTimePlugin"/></plugins>
</configuration>
将MyBatis配置文件加到SpringBoot的配置里,修改application.yml如下
# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDevspring:# 数据源配置,用于连接和管理数据库datasource:# 数据库驱动类名,指定连接MySQL数据库所需的驱动类driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false# 数据库用户名,用于身份验证和授权username: root# 数据库密码,与用户名配合用于身份验证password: Ms123!@## 数据源类型,使用Apache Commons DBCP2提供的基本数据源type: org.apache.commons.dbcp2.BasicDataSource# DBCP2特定的配置,用于管理连接池的大小和行为dbcp2:# 最大空闲连接数,超过该数目的空闲连接将被关闭max-idle: 20# 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常max-wait-millis: 5000# 连接池允许的最大连接数,超过该数目将拒绝新的连接请求max-total: 50# 服务器配置,用于设置应用的访问路径和端口
server:# 应用程序的上下文路径,用于区分不同的服务或模块servlet:context-path: /SBDev# 服务器端口,指定应用监听的网络端口号port: 8001# mybatis配置项
mybatis:# 映射文件路径mapper-locations: classpath:mapper/*.xml# TypeHandler扫描包type-handlers-package: com.sbdev.type.handler# 扫描别名type-aliases-package: com.sbdev.pojo# 配置MyBatis配置文件的位置# 使用classpath指定配置文件位于类路径下,便于资源的统一管理和访问config-location: classpath:mybatis-config.xmllogging:level:# 日志级别root: DEBUG
实际上还有很多配置项,可以自行研究,这样配置完,Spring Boot就会启用这个插件了
数据库事务
在Spring Boot初始化数据源的时候,会同时初始化对应的数据库事务管理器,因此不需要配置任何数据库事务的内容可以直接使用,例如如下代码
package com.sbdev.service.impl;import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao = null;@Override@Cacheable(value="redisCache", key="'redis_user_'+#id")public User getUser(Long id) {return userDao.getUser(id);}@Override// 事务管理器由Spring Boot自动装配,无需自己配置@Transactional(isolation = Isolation.READ_COMMITTED)public Integer insertUser(User user) {return userDao.insertUser(user);}
}
为了方便使用Spring Boot还提供了默认隔离级别的配置,如下所示
spring:# 数据源配置,用于连接和管理数据库datasource:# 配置Hikari连接池的事务隔离级别为读未提交# 这一设置影响使用Hikari连接池的数据库连接的默认事务隔离行为hikari:transaction-isolation: 2# -1:默认隔离级别# 1:读未提交# 2:读已提交# 4:可重复读# 8:串行化# 配置Tomcat连接池的默认事务隔离级别为读未提交# 此设置适用于所有通过Tomcat连接池获取的数据库连接tomcat:default-transaction-isolation: 2# 数据库驱动类名,指定连接MySQL数据库所需的驱动类driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false# 数据库用户名,用于身份验证和授权username: root# 数据库密码,与用户名配合用于身份验证password: Ms123!@## 数据源类型,使用Apache Commons DBCP2提供的基本数据源type: org.apache.commons.dbcp2.BasicDataSource# DBCP2特定的配置,用于管理连接池的大小和行为dbcp2:# 最大空闲连接数,超过该数目的空闲连接将被关闭max-idle: 20# 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常max-wait-millis: 5000# 连接池允许的最大连接数,超过该数目将拒绝新的连接请求max-total: 50# 设置默认事务隔离级别为二级隔离default-transaction-isolation: 2
Spring Boot和Spring MVC
在大部分情况下,在Spring Boot中使用Spring MVC的方法和传统Spring MVC并无太大的不同,但又一些特殊用法
使用WebMvcConfigurer接口
通常我们可以通过实现WebMvcConfiguration接口来自定义Spring MVC的组件,例如需要开发一个用户拦截器,如下代码所示
/*** 用户拦截器类,用于在请求处理的不同阶段执行自定义逻辑。* 实现了Spring MVC的HandlerInterceptor接口。*/
package com.sbdev.interceptor;import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;public class UserInterceptor implements HandlerInterceptor {/*** 在请求处理之前执行的逻辑。* 该方法用于进行权限检查或初始化一些资源。* @param request 当前请求的HttpServletRequest对象* @param response 当前请求的HttpServletResponse对象* @param handler 将要处理请求的目标对象* @return true表示继续处理请求,false表示中断请求处理* @throws Exception 如果在预处理阶段发生异常*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle");return true;}/*** 在请求处理完成后,但在视图渲染之前执行的逻辑。* 该方法可用于进行一些数据处理或清理工作。* @param request 当前请求的HttpServletRequest对象* @param response 当前请求的HttpServletResponse对象* @param handler 处理请求的目标对象* @param modelAndView 视图模型对象,可能为null,表示不返回视图* @throws Exception 如果在后处理阶段发生异常*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle");}/*** 在整个请求处理完成后,包括视图渲染,执行的逻辑。* 该方法可用于进行一些资源的释放或日志记录等清理工作。* @param request 当前请求的HttpServletRequest对象* @param response 当前请求的HttpServletResponse对象* @param handler 处理请求的目标对象* @param ex 在请求处理过程中抛出的异常,可能为null* @throws Exception 如果在完成处理阶段发生异常*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion");}
}
package com.sbdev.config;import com.sbdev.interceptor.UserInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** Web配置类,用于自定义Spring MVC的配置。*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 添加拦截器配置。* @param registry 拦截器注册表,用于注册和管理拦截器。* 这里通过添加UserInterceptor拦截器并指定拦截路径为/user/**,* 实现对用户相关请求的拦截处理。*/public void addInterceptors(InterceptorRegistry registry) {// 添加UserInterceptor拦截器registry.addInterceptor(new UserInterceptor())// 指定拦截器拦截的URL路径.addPathPatterns("/user/**");}
}
这样就可以配置各类Spring MVC组件了
使用SpringBoot的Spring MVC配置
# 应用程序名称,用于标识和区分不同的Spring Boot应用
spring.application.name: SBDevspring:# 数据源配置,用于连接和管理数据库datasource:# 配置Hikari连接池的事务隔离级别为读未提交# 这一设置影响使用Hikari连接池的数据库连接的默认事务隔离行为hikari:transaction-isolation: 2# -1:默认隔离级别# 1:读未提交# 2:读已提交# 4:可重复读# 8:串行化# 配置Tomcat连接池的默认事务隔离级别为读未提交# 此设置适用于所有通过Tomcat连接池获取的数据库连接tomcat:default-transaction-isolation: 2# 数据库驱动类名,指定连接MySQL数据库所需的驱动类driver-class-name: com.mysql.cj.jdbc.Driver# 数据库连接URL,指定连接的数据库地址、端口、数据库名称以及连接参数url: jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false# 数据库用户名,用于身份验证和授权username: root# 数据库密码,与用户名配合用于身份验证password: Ms123!@## 数据源类型,使用Apache Commons DBCP2提供的基本数据源type: org.apache.commons.dbcp2.BasicDataSource# DBCP2特定的配置,用于管理连接池的大小和行为dbcp2:# 最大空闲连接数,超过该数目的空闲连接将被关闭max-idle: 20# 等待连接的最大时间,超过该时间仍未获取到连接将抛出异常max-wait-millis: 5000# 连接池允许的最大连接数,超过该数目将拒绝新的连接请求max-total: 50# 设置默认事务隔离级别为二级隔离default-transaction-isolation: 2# 配置 MVC 框架的相关设置mvc:# 配置用户服务的 Servlet 信息servlet:# 定义 Servlet 的访问路径path: /user# 设置 Servlet 在应用启动时加载的顺序load-on-startup: 1# 配置视图解析的相关设置view:# 定义视图文件的后缀名suffix: .jsp# 定义视图文件的目录前缀prefix: /WEB-INF/jsp/# 配置 Web 应用的国际化设置web:# 设置固定的地域解析器locale-resolver: fixed# 指定应用的默认地域为中文(中国)locale: zh_CN# 配置 Jackson 的相关设置,用于序列化和反序列化日期时间jackson:# 定义日期时间的格式化样式date-format: yyyy-MM-dd HH:mm:ss# 服务器配置,用于设置应用的访问路径和端口
server:# 应用程序的上下文路径,用于区分不同的服务或模块servlet:context-path: /SBDev# 服务器端口,指定应用监听的网络端口号port: 8001# mybatis配置项
mybatis:# 映射文件路径mapper-locations: classpath:mapper/*.xml# TypeHandler扫描包type-handlers-package: com.sbdev.type.handler# 扫描别名type-aliases-package: com.sbdev.pojo# 配置MyBatis配置文件的位置# 使用classpath指定配置文件位于类路径下,便于资源的统一管理和访问config-location: classpath:mybatis-config.xmllogging:level:# 日志级别root: DEBUG
在IDEA里双击Shift键,搜MybatisProperties或者搜WebMvcProperties能看到如下内容,都是Spring Boot配置文件中的配置项
使用转换器
定义转换器,例如用户POJO,可以约定浏览器提交的格式为{id}-{userName}-{sex}-{note}
可以使用Converter机制处理
package com.sbdev.comverter;import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;@Component
public class UserConverter implements Converter<String, User> {/*** 约定提交格式“{id}-{userName}-{sex}-{note}”* @param source 源字符串* @return 用户对象*/@Overridepublic User convert(String source) {if (source == null) {return null;}String []arr = source.split("-");if (arr.length != 4) { // 不符合格式return null;}User user = new User();user.setId(Long.parseLong(arr[0]));user.setUserName(arr[1]);user.setSex(SexEnum.getSexEnum(Integer.parseInt(arr[2])));user.setNote(arr[3]);return user;}
}
这里标注了@Component
,这意味着它将被扫描装配到Spring IoC容器中,然后可以通过控制器来进行测试
@GetMapping("/print/{user}")public User print(User user) {return user;}
然后在浏览器中输入输入地址user/print/1-username-0-note
测试转换器是否生效即可,这里我们并没有给Spring MVC注册UserConverter,但是Spring MVC还是使用了UserConverter去转换参数,这事因为Spring Boot已经为我们做了注册,但前提是需要将它们装配到Spring IoC容器中去
在Spring MVC的配置类WebMvcAutoConfiguration中调用了ApplicationConversionService的静态addBeans方法,源码如下
/*** 将豆类工厂中的转换器、格式化器、解析器注册到格式化器注册表中。* 此方法旨在支持自动注册由Spring Bean工厂提供的各种转换器和格式化器,以增强系统的数据处理能力。* @param registry 格式化器注册表,用于接收和管理转换器、格式化器、解析器。* @param beanFactory Spring的Bean工厂,用于获取所有实现了转换器、格式化器、解析器接口的Bean实例。*/public static void addBeans(FormatterRegistry registry, ListableBeanFactory beanFactory) {// 使用LinkedHashSet来保存转换器、格式化器、解析器实例,以保持注册顺序。Set<Object> beans = new LinkedHashSet<>();// 分别获取并合并GenericConverter、Converter、Printer、Parser类型的Bean实例。beans.addAll(beanFactory.getBeansOfType(GenericConverter.class).values());beans.addAll(beanFactory.getBeansOfType(Converter.class).values());beans.addAll(beanFactory.getBeansOfType(Printer.class).values());beans.addAll(beanFactory.getBeansOfType(Parser.class).values());// 遍历所有获取到的Bean实例。Iterator var3 = beans.iterator();while(var3.hasNext()) {Object bean = var3.next();// 根据实例类型,分别注册到对应的格式化器注册表中。// 这里使用了Java的实例类型检查来决定注册哪种类型的实例。if (bean instanceof GenericConverter genericConverter) {registry.addConverter(genericConverter);} else if (bean instanceof Converter<?, ?> converter) {registry.addConverter(converter);} else if (bean instanceof Formatter<?> formatter) {registry.addFormatter(formatter);} else if (bean instanceof Printer<?> printer) {registry.addPrinter(printer);} else if (bean instanceof Parser<?> parser) {registry.addParser(parser);}}}
SpringBoot会将所有在Spring IoC容器中的Converter、GenericConverter、Formatter都注册到Spring MVC的转换机制中,所以即使我们没有显示的注册,Spring MVC也能发现我们自定义的转换器
Spring Boot 使用Redis
使用Redis需要添加相关依赖
<!-- 添加Tomcat-JDBC依赖 --><dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-jdbc</artifactId></dependency><!-- 引入Spring Boot的Redis支持,但排除Lettuce客户端 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><!--不依赖Redis的异步客户端lettuce--><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions></dependency><!-- 引入Jedis作为Redis的同步客户端 --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency>
Spring Boot默认依赖Lettuce客户端,但这里排除了它,因为Lettuce是一个可伸缩线程安全的Redis客户端,多个线程可以共享一个Redis连接,所以会牺牲一部分性能,但一般来说缓存对线程安全的需求并不高,更注重性能,而Jedis是一种多线程非安全的客户端,性能更好
配置Redis
data:redis:host: 127.0.0.1port: 6379jedis:pool:# 最大连接数max-active: 20# 最大等待时间max-wait: 2000ms# 最大空闲连接数max-idle: 10# 最小空闲等待连接数min-idle: 5# # 哨兵模式配置# sentinel:# # 主服务器# master: 192.168.80.130:26379# # 节点# nodes: 192.168.80.131:26379, 192.168.80.132:26379# # 集群配置# cluster:# # 集群节点# nodes: 192.168.80.133:7001, 192.168.80.133:7002, 192.168.80.133:7003, 192.168.80.133:7004, 192.168.80.133:7005, 192.168.80.133:7006# # 单一连接最大转向次数# max-redirects: 10
在Spring Boot中自动配置类为RedisAutoConfiguration属性类为RedisProperties,更多的配置可以在这里边找到配置好后便可以使用Redis了
package com.sbdev.controller;import com.sbdev.dic.SexEnum;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;@RestController
@RequestMapping("/redis")
public class RedisController {// StringRedisTemplate是RedisTemplate的子类,但是该属性名“redisTemplate”和Bean名称一致,// 因此可以使用@Autowired注入,如果属性名称不为“redisTemplate”,则注入失败,抛出异常@Autowired
// private RedisTemplate<Object, Object> redisTemplate = null;private RedisTemplate<String, Object> redisTemplate = null;// 注入StringRedisTemplate@Autowiredprivate StringRedisTemplate stringRedisTemplate = null;// 测试字符串@GetMapping("/string/{key}/{value}")public Map<String, String> string(@PathVariable("key") String key,@PathVariable("value") String value) {stringRedisTemplate.opsForValue().set(key, value);Map<String, String> result = new HashMap<>();result.put(key, value);return result;}// 测试对象@GetMapping("/object/{id}")public User object(@PathVariable("id") Long id){User user = new User();user.setId(id);user.setUserName("user_name" + id);user.setNote("note_" + id);user.setSex(SexEnum.getSexEnum(id.intValue() % 2));redisTemplate.opsForValue().set("user_" + id, user);return user;}@Autowiredprivate UserService userService = null;@GetMapping("/user/{id}")public User getUser(@PathVariable("id") Long id) {return userService.getUser(id);}
}
然后根据控制器中的地址请求即可完成测试即可
Redis缓存管理器
首先在Spring Boot的配置文件中添加缓存配置
data:redis:host: 127.0.0.1port: 6379jedis:pool:# 最大连接数max-active: 20# 最大等待时间max-wait: 2000ms# 最大空闲连接数max-idle: 10# 最小空闲等待连接数min-idle: 5# # 哨兵模式配置# sentinel:# # 主服务器# master: 192.168.80.130:26379# # 节点# nodes: 192.168.80.131:26379, 192.168.80.132:26379# # 集群配置# cluster:# # 集群节点# nodes: 192.168.80.133:7001, 192.168.80.133:7002, 192.168.80.133:7003, 192.168.80.133:7004, 192.168.80.133:7005, 192.168.80.133:7006# # 单一连接最大转向次数# max-redirects: 10# 缓存管理器配置cache:# 缓存管理器名称cache-names: redisCache# 缓存管理器类型,可不配,Spring Boot会自动发现# type: redisredis:# 十分钟超时时间,配置为0 则永不超时time-to-live: 600000ms# 缓存空值cache-null-values: true# 缓存key前缀key-prefix: 'sbdev::'# key是否使用前缀use-key-prefix: true
然后通过缓存注解的方式来使用Redis
package com.sbdev.service.impl;import com.sbdev.dao.UserDao;
import com.sbdev.pojo.User;
import com.sbdev.service.UserService;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao = null;/*** 从Redis缓存中获取用户信息。* 使用了@Cacheable注解,使得该方法的结果可以被缓存。如果缓存中已存在对应键的用户信息,* 则直接从缓存中返回,避免了再次查询数据库。缓存的键由'redis_user_'和用户ID拼接而成,* 保证了键的唯一性。* @param id 用户的ID,作为查询和缓存键的一部分。* @return 用户对象,包含用户信息。*/@Override@Cacheable(value = "redisCache", key = "'redis_user_'+#id")public User getUser(Long id) {// 通过UserDao查询Redis中的用户信息。return userDao.getUser(id);}@Override// 事务管理器由Spring Boot自动装配,无需自己配置@Transactional(isolation = Isolation.READ_COMMITTED)public Integer insertUser(User user) {return userDao.insertUser(user);}}
然后通过控制器对应的方法进行测试
@Autowiredprivate UserService userService = null;@GetMapping("/user/{id}")public User getUser(@PathVariable("id") Long id) {return userService.getUser(id);}