Spring对JDBC的最佳实践--上
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring对JDBC的最佳实践--上相关的知识,希望对你有一定的参考价值。
Spring对JDBC的最佳实践--上
引子
在一开始,会介绍一下原生JDBC的使用,此时不会涉及到spring的使用,但是我们还是利用yml文件来存放数据源配置信息,因此我们需要一个yaml读取的工具类
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
工具类:
package org.TX;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 大忽悠
* @create 2022/4/28 17:09
*/
@Slf4j
public class YamlUtil
private Yaml yaml;
private Map<String, Map<String, Object>> yamlContent;
private Map<String, String> resCache;
private final String ymlFilePath;
private final String KEY_DELIMITER = "\\\\.";
public YamlUtil(String ymlFilePath)
this.ymlFilePath = ymlFilePath;
@SneakyThrows
public String get(String key)
if (resCache != null && resCache.containsKey(key))
return resCache.get(key);
//懒加载
if (yaml == null)
//初始化yaml
initYaml();
//查询,放入缓存
return queryAndPutCache(key);
private void initYaml() throws FileNotFoundException
try
yaml=new Yaml();
yamlContent = yaml.load(YamlUtil.class.getClassLoader().getResourceAsStream(ymlFilePath));
catch (YAMLException yamlException)
//尝试去文件系统中定位yaml文件
File file = new File(ymlFilePath);
if(!file.exists())
throw new YAMLException("classPath和文件系统中无法找到名为" + ymlFilePath + "的文件");
yamlContent=yaml.load(new FileInputStream(file));
private String queryAndPutCache(String key)
String[] keys = key.split(KEY_DELIMITER);
String value = extractValue(keys, yamlContent, 0);
if (resCache == null)
resCache = new ConcurrentHashMap<>();
resCache.put(key, "value");
return value;
private String extractValue(String[] keys, Map<String, Map<String, Object>> yamlContent, int index)
if (index == keys.length)
return null;
Object valueMap = yamlContent.get(keys[index]);
if (valueMap == null || !(valueMap instanceof Map))
return valueMap instanceof String ? (String) valueMap:valueMap.toString();
return extractValue(keys, (Map<String, Map<String, Object>>) valueMap, index + 1);
基于Template的JDBC使用方式
JDBC的尴尬
首先JDBC的api设计偏向于底层化发展,因此对于开发者而言,使用起来会有大量的雷同重复代码产生。
并且烦人的资源关闭问题,也让人头大不止。
简单的jdbc工具类一览:
public class JdbcUtil
private final String yamlFilePath;
private Connection connection;
private Statement statement;
public JdbcUtil(String yamlFilePath)
this.yamlFilePath = yamlFilePath;
private synchronized Connection getConn() throws SQLException
if(connection==null)
YamlUtil yamlUtil=new YamlUtil(yamlFilePath);
String url = yamlUtil.get("spring.datasource.url");
String username = yamlUtil.get("spring.datasource.username");
String password = yamlUtil.get("spring.datasource.password");
String driveClassName = yamlUtil.get("spring.datasource.driver-class-name");
connection = DriverManager.getConnection(url,username,password);
return connection;
private synchronized Statement createStatement() throws SQLException
if(statement==null)
Connection connection = getConn();
statement=connection.createStatement();
return statement;
public int updateOrInsertOrDelete(String sql) throws SQLException
return createStatement().executeUpdate(sql);
public void close()
if(statement!=null)
try
statement.close();
catch (SQLException e)
e.printStackTrace();
if(connection!=null)
try
connection.close();
catch (SQLException e)
e.printStackTrace();
可以看出,jdbc有非常多的重复性代码需要封装,这一点通过我们简单的封装一个jdbc工具类就可以看出,因此我们需要一个足够方便的,对jdbc的工具类,来简化我们对jdbc的操作,此时Spring的JdbcTemplate就完成了这件事情。
JdbcTemplate的诞生
JdbcTemplate的演化
模板方法模式与CallBack之间的关系
CallBack接口与模板方法类之间的关系可以看做是服务于被服务的关系,模板方法类想要CallBack做事,就要提供相应的资源。
CallBack使用提供的资源做事,完事之后,模板方法来处理公开的资源,CallBack接口不需要关心这些。
因为一般模板方法过程中需要变化的方法一般都是抽象的,因此当前模板类也是抽象的,这样一来就需要实现非常多的子类,如果想避免这种情况,最好的方法就是将需要变化的方法以回调接口的形式公开。
JDBC继承结构
@Nullable
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try
stmt = con.createStatement();
applyStatementSettings(stmt);
//回调接口被调用
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
catch (SQLException ex)
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
finally
if (closeResources)
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
重载execute方法:
@Override
public void execute(final String sql) throws DataAccessException
if (logger.isDebugEnabled())
logger.debug("Executing SQL statement [" + sql + "]");
/**
* Callback to execute the statement.
*/
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider
@Override
@Nullable
public Object doInStatement(Statement stmt) throws SQLException
stmt.execute(sql);
return null;
@Override
public String getSql()
return sql;
//执行上面给出的那个重载方法,传入回调接口
execute(new ExecuteStatementCallback(), true);
使用DataSourceUtils进行Connection的管理
这里给大家回顾一下JDBC是如何处理事务的,就明白为啥要把Connection绑定到当前线程上去了
public boolean giveMoney(String Giver,String Revicer,int money)
//1.获取连接
Connection conn= null;
PreparedStatement pstmt1=null;
PreparedStatement pstmt2=null;
try
conn = JDBCUtil.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
String sql1="update login set money=money-? where name= ?";
String sql2="update login set money=money+? where name= ?";
//3.获取执行sql的对象
pstmt1=conn.prepareStatement(sql1);
pstmt2=conn.prepareStatement(sql2);
//给?赋值
pstmt1.setInt(1,500);
pstmt1.setString(2,"大忽悠");
pstmt2.setInt(1,500);
pstmt2.setString(2,"小朋友");
//4.执行
pstmt1.executeUpdate();
//异常的出现
String s=null;
s.getBytes(StandardCharsets.UTF_8);
pstmt2.executeUpdate();
//结束事务
conn.commit();
return true;
catch (SQLException throwables)
//事务进行回滚
try
if(conn!=null)
conn.rollback();
catch (SQLException e)
e.printStackTrace();
throwables.printStackTrace();
finally
JDBCUtil.close(pstmt1,conn);
JDBCUtil.close(pstmt2,conn);
return false;
使用NativeJdbcExtractor来获得"真相"
控制JdbcTemplate的行为
protected void applyStatementSettings(Statement stmt) throws SQLException
int fetchSize = getFetchSize();
if (fetchSize != -1)
stmt.setFetchSize(fetchSize);
int maxRows = getMaxRows();
if (maxRows != -1)
stmt.setMaxRows(maxRows);
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
SQLException到DataAccessException体系的转义
public interface SQLExceptionTranslator
@Nullable
DataAccessException translate(String task, @Nullable String sql, SQLException ex);
SQLErrorCodeSQLExceptionTranslator的doTranslate方法:
@Override
@Nullable
protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex)
SQLException sqlEx = ex;
//首先对批处理异常进行处理
if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null)
SQLException nestedSqlEx = sqlEx.getNextException();
if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null)
sqlEx = nestedSqlEx;
// First, try custom translation from overridden method.
//首先尝试应用自定义异常翻译方法
DataAccessException dae = customTranslate(task, sql, sqlEx);
//如果返回值不为null,那就自定义异常翻译处理后的结果
if (dae != null)
return dae;
// Next, try the custom SQLException translator, if available.
//获取sql错误码集合
SQLErrorCodes sqlErrorCodes = getSqlErrorCodes();
if (sqlErrorCodes != null)
//尝试获取SQLErrorCodes中设置的自定义异常翻译器
SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator();
if (customTranslator != null)
//如果存在,就直接应用
DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
if (customDex != null)
return customDex;
// Check SQ以上是关于Spring对JDBC的最佳实践--上的主要内容,如果未能解决你的问题,请参考以下文章
Flink JDBC Connector:Flink 与数据库集成最佳实践
Flink JDBC Connector:Flink 与数据库集成最佳实践
Flink JDBC Connector:Flink 与数据库集成最佳实践