JDK20 + SpringBoot 3.1.0 + JdbcTemplate 使用案例详解
作者:猪悟道
通过 JdbcTemplate 直接执行 SQL 语句,结合源码动态编译即可方便实现动态修改代码逻辑的效果,这篇文章主要介绍了JDK20 + SpringBoot 3.1.0 + JdbcTemplate 使用,需要的朋友可以参考下
通过 JdbcTemplate 直接执行 SQL 语句,结合源码动态编译即可方便实现动态修改代码逻辑的效果
一.测试数据库 Postgres
-- public.tb_rabbit_basic definition -- Drop table -- DROP TABLE public.tb_rabbit_basic; CREATE TABLE public.tb_rabbit_basic ( id int4 NULL, animal_name varchar NULL, country varchar NULL );
二.SpringBoot项目
1.Pom 依赖
<?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>org.example</groupId> <artifactId>JdbcTemplateDemo</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>18</source> <target>18</target> </configuration> </plugin> </plugins> </build> <properties> <maven.compiler.source>20</maven.compiler.source> <maven.compiler.target>20</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring-boot.version>3.1.0</spring-boot.version> </properties> <!--配置阿里云依赖包和插件仓库--> <repositories> <repository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/central/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.16</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!-- mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.2</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <!-- pgsql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.6.0</version> </dependency> </dependencies> </project>
2.配置文件
server: port: 8081 spring: datasource: postgres: readTimeout: 259200000 # 3 * 24 * 60 * 60 * 1000 druid: username: postgres password: 123456 url: jdbc:postgresql://127.0.0.1:5432/wiki_animal_db driverClassName: org.postgresql.Driver type: com.alibaba.druid.pool.DruidDataSource # 下面为连接池的补充设置,应用到上面所有数据源中 # 初始化大小,最小,最大 initial-size: 5 min-idle: 5 max-active: 20 # 配置获取连接等待超时的时间 max-wait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 validation-query: select version() test-while-idle: true test-on-borrow: false test-on-return: false # 打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,wall用于防火墙 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
3.启动类
package org.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author moon */ @SpringBootApplication public class JdbcApp { public static void main(String[] args) { SpringApplication.run(JdbcApp.class, args); } }
4.数据源配置类
package org.example.config; import com.alibaba.druid.pool.DruidDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.core.JdbcTemplate; import java.sql.SQLException; import java.util.Properties; /** * @author moon * @date 2023-09-12 12:00 * @since 1.8 */ @Slf4j @Configuration public class PostgresDataSource { /** * Postgres readTimeout 超时 暂定 3D 可能导致存在大量 socket 死链接 */ @Value("${spring.datasource.postgres.readTimeout}") private int readTimeout; @Bean(name = "druidProperties") @ConfigurationProperties(prefix = "spring.datasource") public Properties druidProperties(){ return new Properties(); } /** * @description: 数据源 * @params: [properties] * @return: com.alibaba.druid.pool.DruidDataSource * @create: 2023-09-12 */ @Primary @Bean(name = "druidDataSource") public DruidDataSource druidDataSource(@Qualifier("druidProperties") Properties properties){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.configFromPropety(properties); try { druidDataSource.setSocketTimeout(readTimeout); druidDataSource.init(); } catch (SQLException e) { log.error("Postgres Datasource Init Exception:",e); } return druidDataSource; } /** * jdbc template * @param druidDataSource * @return */ @Bean(name = "postgresTemplate") public JdbcTemplate postgresTemplate(@Qualifier("druidDataSource") DruidDataSource druidDataSource){ return new JdbcTemplate(druidDataSource); } }
5.实体对象类包装类
用于配置实体对象类,方便解析 JdbcTemplate 查询的返回值
package org.example.config; import lombok.extern.slf4j.Slf4j; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.NotWritablePropertyException; import org.springframework.beans.TypeMismatchException; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import java.beans.PropertyDescriptor; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.*; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * @author moon * @date 2023-09-11 18:08 * @since 1.8 */ @Slf4j @Component public class ColumnRowMap { private Map<String,MultiColumnRowMapper> map = new HashMap<>(16); Semaphore semaphore = new Semaphore(1); /** * 获取类包装对象 * @param clazz * @return */ public MultiColumnRowMapper getColumnRowMap(Class<?> clazz) { while (true){ boolean acquire = false; try { acquire = semaphore.tryAcquire(3, TimeUnit.SECONDS); if (acquire){ MultiColumnRowMapper mapper = map.get(clazz.getName()); if (null == mapper){ mapper = new MultiColumnRowMapper<>(clazz); map.put(clazz.getName(),mapper); } //返回 return mapper; } } catch (InterruptedException e) { log.error("get column row map exception:",e); } finally { if (acquire){ semaphore.release(); } } } } static class MultiColumnRowMapper<T> implements RowMapper<T> { /** * 日志 */ protected final Log logger = LogFactory.getLog(this.getClass()); /** * 转换类型 */ @Nullable private Class<T> requiredType; /** * 缓存类属性 */ @Nullable private Map<String, PropertyDescriptor> mappedFields; @Nullable private Set<String> mappedProperties; private boolean primitivesDefaultedForNullValue = true; /** * 是否校验属性一致 */ private boolean checkFullyPopulated = false; public void setCheckFullyPopulated(boolean checkFullyPopulated) { this.checkFullyPopulated = checkFullyPopulated; } public boolean isCheckFullyPopulated() { return this.checkFullyPopulated; } public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) { this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue; } public boolean isPrimitivesDefaultedForNullValue() { return this.primitivesDefaultedForNullValue; } /** * 构造并解析目标类属性信息 * @param requiredType */ public MultiColumnRowMapper(Class<T> requiredType) { this.requiredType = requiredType; init(); } /** * 解析属性 */ private void init(){ PropertyDescriptor[] var2 = BeanUtils.getPropertyDescriptors(requiredType); int var3 = var2.length; this.mappedFields = new HashMap(var3); this.mappedProperties = new HashSet(var3); for(int var4 = 0; var4 < var3; ++var4) { PropertyDescriptor pd = var2[var4]; if (pd.getWriteMethod() != null) { String lowerCaseName = this.lowerCaseName(pd.getName()); this.mappedFields.put(lowerCaseName, pd); String underscoreName = this.underscoreName(pd.getName()); if (!lowerCaseName.equals(underscoreName)) { this.mappedFields.put(underscoreName, pd); } this.mappedProperties.add(pd.getName()); } } } /** * 将返回信息转为指定类对象 * @param rs * @param rowNumber * @return * @throws SQLException */ @Nullable public T mapRow(ResultSet rs, int rowNumber) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); T mappedObject = BeanUtils.instantiateClass(requiredType); Set<String> populatedProperties = this.isCheckFullyPopulated() ? new HashSet() : null; BeanWrapperImpl bw = new BeanWrapperImpl(); bw.setBeanInstance(mappedObject); PropertyDescriptor pd; for(int index = 1; index <= columnCount; ++index) { String column = JdbcUtils.lookupColumnName(rsmd, index); String field = this.lowerCaseName(StringUtils.delete(column, " ")); pd = this.mappedFields != null ? this.mappedFields.get(field) : null; if (pd != null) { try { Object value = this.getColumnValue(rs, index, pd); if (rowNumber == 0 && this.logger.isDebugEnabled()) { this.logger.debug("Mapping column '" + column + "' to property '" + pd.getName() + "' of type '" + ClassUtils.getQualifiedName(pd.getPropertyType()) + "'"); } try { bw.setPropertyValue(pd.getName(), value); } catch (TypeMismatchException var14) { if (value != null || !this.primitivesDefaultedForNullValue) { throw var14; } if (this.logger.isDebugEnabled()) { this.logger.debug("Intercepted TypeMismatchException for row " + rowNumber + " and column '" + column + "' with null value when setting property '" + pd.getName() + "' of type '" + ClassUtils.getQualifiedName(pd.getPropertyType()) + "' on object: " + mappedObject, var14); } } if (populatedProperties != null) { populatedProperties.add(pd.getName()); } } catch (NotWritablePropertyException var15) { throw new DataRetrievalFailureException("Unable to map column '" + column + "' to property '" + pd.getName() + "'", var15); } } } //校验属性一致性 if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) { throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields necessary to populate object of " + this.requiredType.getName() + ": " + this.mappedProperties); } else { return mappedObject; } } @Nullable protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException { return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType()); } protected String lowerCaseName(String name) { return name.toLowerCase(Locale.US); } protected String underscoreName(String name) { if (!StringUtils.hasLength(name)) { return ""; } else { StringBuilder result = new StringBuilder(); result.append(Character.toLowerCase(name.charAt(0))); for(int i = 1; i < name.length(); ++i) { char c = name.charAt(i); if (Character.isUpperCase(c)) { result.append('_').append(Character.toLowerCase(c)); } else { result.append(c); } } return result.toString(); } } } }
6.测试用实体对象
1.基类
package org.example.entity; import lombok.Data; /** * @author moon * @date 2023-09-12 10:45 * @since 1.8 */ @Data public class BaseAnimal { private int id; private String animalName; private String country; }
2.扩展类
package org.example.entity; /** * @author moon * @date 2023-09-12 10:48 * @since 1.8 */ public class Rabbit extends BaseAnimal{ }
7.测试类
package org.example.controller; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.example.config.ColumnRowMap; import org.example.entity.Rabbit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * @author moon * @date 2023-09-12 11:52 * @since 1.8 */ @Slf4j @RestController @RequestMapping("/animal") public class AnimalController { @Resource(name = "postgresTemplate") private JdbcTemplate postgresTemplate; @Autowired private ColumnRowMap columnRowMap; /** * 插入 通过 ? 参数占位符 */ @GetMapping("/insert") public void insert(){ postgresTemplate.update("INSERT INTO PUBLIC.TB_RABBIT_BASIC (ID, ANIMAL_NAME, COUNTRY) VALUES (?,?,?)",18,"海棠兔","法国"); } /** * 批量插入 */ @GetMapping("/batchInsert") public void batchInsert(){ List<Object[]> list = new ArrayList<>(3); list.add(new Object[]{19,"海棠兔","法国"}); list.add(new Object[]{20,"喜马拉雅兔","中国"}); list.add(new Object[]{30,"野兔","比利时"}); postgresTemplate.batchUpdate("INSERT INTO PUBLIC.TB_RABBIT_BASIC (ID, ANIMAL_NAME, COUNTRY) VALUES (?,?,?)",list); } /** * 更新 */ @GetMapping("/update") public void update(){ postgresTemplate.update("UPDATE PUBLIC.TB_RABBIT_BASIC SET COUNTRY = ? WHERE ID = ?","法国+",19); } /** * 更新 */ @GetMapping("/batchUpdate") public void batchUpdate(){ List<Object[]> list = new ArrayList<>(3); list.add(new Object[]{"法国+++",19}); list.add(new Object[]{"中国+++",20}); list.add(new Object[]{"比利时+++",30}); postgresTemplate.batchUpdate("UPDATE PUBLIC.TB_RABBIT_BASIC SET COUNTRY = ? WHERE ID = ?",list); } /** * 删除 */ @GetMapping("/delete") public void delete(){ postgresTemplate.update("DELETE FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?",19); } /** * 批量删除 */ @GetMapping("/batchDelete") public int[] batchDelete(){ List<Object[]> list = new ArrayList<>(); list.add(new Object[]{19}); list.add(new Object[]{20}); list.add(new Object[]{30}); int[] result = postgresTemplate.batchUpdate("DELETE FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?",list); return result; } /** * 查询 select * * @return */ @GetMapping("/queryForMap") public Map<String, Object> queryForMap(){ return postgresTemplate.queryForMap("SELECT * FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?",18); } /** * 查询 query for row set * @return */ @GetMapping("/queryForRowSet") public void queryForRowSet(){ SqlRowSet rowSet = postgresTemplate.queryForRowSet("SELECT * FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?",18); while (rowSet.next()){ int rowId = rowSet.getRow(); Integer ID = rowSet.getInt("ID"); String ANIMAL_NAME = rowSet.getString("ANIMAL_NAME"); String COUNTRY = rowSet.getString("COUNTRY"); log.info("rowId {} id {} animalName {} country {}",rowId,ID,ANIMAL_NAME,COUNTRY); } } /** * 查询 query for object * @return */ @GetMapping("/queryForObject") public Object queryForObject(){ return postgresTemplate.queryForObject("SELECT ID AS id FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?", Integer.class,18); } /** * 查询 query for object * @return */ @GetMapping("/queryForObjectMapper") public Object queryForObjectMapper(){ return postgresTemplate.queryForObject("SELECT ID AS id, ANIMAL_NAME AS animalName, COUNTRY AS country FROM PUBLIC.TB_RABBIT_BASIC WHERE ID = ?",columnRowMap.getColumnRowMap(Rabbit.class),18); } /** * 查询 query for object * @return */ @GetMapping("/queryForList") public List<Map<String, Object>> queryForList(){ return postgresTemplate.queryForList("SELECT ID AS id, ANIMAL_NAME AS animalName, COUNTRY AS country FROM PUBLIC.TB_RABBIT_BASIC"); } /** * 查询 query for object class * @return */ @GetMapping("/queryForListClass") public List<Rabbit> queryForListClass(){ return postgresTemplate.query("SELECT ID AS id, ANIMAL_NAME AS animalName, COUNTRY AS country FROM PUBLIC.TB_RABBIT_BASIC",columnRowMap.getColumnRowMap(Rabbit.class)); } }
以批量查询为例:http://127.0.0.1:8081/animal/queryForListClass
三.SpringBoot 封装的工具类演示
import org.example.entity.Rabbit; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapperImpl; /** * @author moon * @date 2023-09-13 20:38 * @since 1.8 */ public class Test { public static void main(String[] args) { Object t = BeanUtils.instantiateClass(Rabbit.class); BeanWrapperImpl bean = new BeanWrapperImpl(t); bean.setPropertyValue("id",1); System.out.println(t); } }
到此这篇关于JDK20 + SpringBoot 3.1.0 + JdbcTemplate 使用的文章就介绍到这了,更多相关JDK20 + SpringBoot 3.1.0 + JdbcTemplate 使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!