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知识点回顾教程


简单的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的最佳实践--上的主要内容,如果未能解决你的问题,请参考以下文章

Spring系列

Flink JDBC Connector:Flink 与数据库集成最佳实践

Flink JDBC Connector:Flink 与数据库集成最佳实践

Flink JDBC Connector:Flink 与数据库集成最佳实践

Flink JDBC Connector:Flink 与数据库集成最佳实践

Spring JDBC RowMapper 用于急切获取