Spring对JDBC的最佳实践--上

x33g5p2x  于2022-05-05 转载在 Spring  
字(26.3k)|赞(0)|评价(0)|浏览(885)

引子

在一开始,会介绍一下原生JDBC的使用,此时不会涉及到spring的使用,但是我们还是利用yml文件来存放数据源配置信息,因此我们需要一个yaml读取的工具类

  1. <dependency>
  2. <groupId>org.yaml</groupId>
  3. <artifactId>snakeyaml</artifactId>
  4. <version>1.26</version>
  5. </dependency>

工具类:

  1. package org.TX;
  2. import lombok.SneakyThrows;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.yaml.snakeyaml.Yaml;
  5. import org.yaml.snakeyaml.error.YAMLException;
  6. import java.io.File;
  7. import java.io.FileInputStream;
  8. import java.io.FileNotFoundException;
  9. import java.util.Map;
  10. import java.util.concurrent.ConcurrentHashMap;
  11. /**
  12. * @author 大忽悠
  13. * @create 2022/4/28 17:09
  14. */
  15. @Slf4j
  16. public class YamlUtil {
  17. private Yaml yaml;
  18. private Map<String, Map<String, Object>> yamlContent;
  19. private Map<String, String> resCache;
  20. private final String ymlFilePath;
  21. private final String KEY_DELIMITER = "\\.";
  22. public YamlUtil(String ymlFilePath) {
  23. this.ymlFilePath = ymlFilePath;
  24. }
  25. @SneakyThrows
  26. public String get(String key) {
  27. if (resCache != null && resCache.containsKey(key)) {
  28. return resCache.get(key);
  29. }
  30. //懒加载
  31. if (yaml == null) {
  32. //初始化yaml
  33. initYaml();
  34. }
  35. //查询,放入缓存
  36. return queryAndPutCache(key);
  37. }
  38. private void initYaml() throws FileNotFoundException {
  39. try {
  40. yaml=new Yaml();
  41. yamlContent = yaml.load(YamlUtil.class.getClassLoader().getResourceAsStream(ymlFilePath));
  42. } catch (YAMLException yamlException) {
  43. //尝试去文件系统中定位yaml文件
  44. File file = new File(ymlFilePath);
  45. if(!file.exists()){
  46. throw new YAMLException("classPath和文件系统中无法找到名为" + ymlFilePath + "的文件");
  47. }
  48. yamlContent=yaml.load(new FileInputStream(file));
  49. }
  50. }
  51. private String queryAndPutCache(String key) {
  52. String[] keys = key.split(KEY_DELIMITER);
  53. String value = extractValue(keys, yamlContent, 0);
  54. if (resCache == null) {
  55. resCache = new ConcurrentHashMap<>();
  56. }
  57. resCache.put(key, "value");
  58. return value;
  59. }
  60. private String extractValue(String[] keys, Map<String, Map<String, Object>> yamlContent, int index) {
  61. if (index == keys.length) {
  62. return null;
  63. }
  64. Object valueMap = yamlContent.get(keys[index]);
  65. if (valueMap == null || !(valueMap instanceof Map)) {
  66. return valueMap instanceof String ? (String) valueMap:valueMap.toString();
  67. }
  68. return extractValue(keys, (Map<String, Map<String, Object>>) valueMap, index + 1);
  69. }
  70. }

基于Template的JDBC使用方式

JDBC的尴尬

首先JDBC的api设计偏向于底层化发展,因此对于开发者而言,使用起来会有大量的雷同重复代码产生。

并且烦人的资源关闭问题,也让人头大不止。

JDBC知识点回顾教程

简单的jdbc工具类一览:

  1. public class JdbcUtil {
  2. private final String yamlFilePath;
  3. private Connection connection;
  4. private Statement statement;
  5. public JdbcUtil(String yamlFilePath) {
  6. this.yamlFilePath = yamlFilePath;
  7. }
  8. private synchronized Connection getConn() throws SQLException {
  9. if(connection==null){
  10. YamlUtil yamlUtil=new YamlUtil(yamlFilePath);
  11. String url = yamlUtil.get("spring.datasource.url");
  12. String username = yamlUtil.get("spring.datasource.username");
  13. String password = yamlUtil.get("spring.datasource.password");
  14. String driveClassName = yamlUtil.get("spring.datasource.driver-class-name");
  15. connection = DriverManager.getConnection(url,username,password);
  16. }
  17. return connection;
  18. }
  19. private synchronized Statement createStatement() throws SQLException {
  20. if(statement==null){
  21. Connection connection = getConn();
  22. statement=connection.createStatement();
  23. }
  24. return statement;
  25. }
  26. public int updateOrInsertOrDelete(String sql) throws SQLException {
  27. return createStatement().executeUpdate(sql);
  28. }
  29. public void close(){
  30. if(statement!=null){
  31. try {
  32. statement.close();
  33. } catch (SQLException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. if(connection!=null){
  38. try {
  39. connection.close();
  40. } catch (SQLException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }
  45. }

可以看出,jdbc有非常多的重复性代码需要封装,这一点通过我们简单的封装一个jdbc工具类就可以看出,因此我们需要一个足够方便的,对jdbc的工具类,来简化我们对jdbc的操作,此时Spring的JdbcTemplate就完成了这件事情。

JdbcTemplate的诞生

JdbcTemplate的演化

模板方法模式与CallBack之间的关系

CallBack接口与模板方法类之间的关系可以看做是服务于被服务的关系,模板方法类想要CallBack做事,就要提供相应的资源。

CallBack使用提供的资源做事,完事之后,模板方法来处理公开的资源,CallBack接口不需要关心这些。

因为一般模板方法过程中需要变化的方法一般都是抽象的,因此当前模板类也是抽象的,这样一来就需要实现非常多的子类,如果想避免这种情况,最好的方法就是将需要变化的方法以回调接口的形式公开。

JDBC继承结构

  1. @Nullable
  2. private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
  3. Assert.notNull(action, "Callback object must not be null");
  4. Connection con = DataSourceUtils.getConnection(obtainDataSource());
  5. Statement stmt = null;
  6. try {
  7. stmt = con.createStatement();
  8. applyStatementSettings(stmt);
  9. //回调接口被调用
  10. T result = action.doInStatement(stmt);
  11. handleWarnings(stmt);
  12. return result;
  13. }
  14. catch (SQLException ex) {
  15. // Release Connection early, to avoid potential connection pool deadlock
  16. // in the case when the exception translator hasn't been initialized yet.
  17. String sql = getSql(action);
  18. JdbcUtils.closeStatement(stmt);
  19. stmt = null;
  20. DataSourceUtils.releaseConnection(con, getDataSource());
  21. con = null;
  22. throw translateException("StatementCallback", sql, ex);
  23. }
  24. finally {
  25. if (closeResources) {
  26. JdbcUtils.closeStatement(stmt);
  27. DataSourceUtils.releaseConnection(con, getDataSource());
  28. }
  29. }
  30. }

重载execute方法:

  1. @Override
  2. public void execute(final String sql) throws DataAccessException {
  3. if (logger.isDebugEnabled()) {
  4. logger.debug("Executing SQL statement [" + sql + "]");
  5. }
  6. /**
  7. * Callback to execute the statement.
  8. */
  9. class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
  10. @Override
  11. @Nullable
  12. public Object doInStatement(Statement stmt) throws SQLException {
  13. stmt.execute(sql);
  14. return null;
  15. }
  16. @Override
  17. public String getSql() {
  18. return sql;
  19. }
  20. }
  21. //执行上面给出的那个重载方法,传入回调接口
  22. execute(new ExecuteStatementCallback(), true);
  23. }
使用DataSourceUtils进行Connection的管理

这里给大家回顾一下JDBC是如何处理事务的,就明白为啥要把Connection绑定到当前线程上去了

  1. public boolean giveMoney(String Giver,String Revicer,int money) {
  2. //1.获取连接
  3. Connection conn= null;
  4. PreparedStatement pstmt1=null;
  5. PreparedStatement pstmt2=null;
  6. try {
  7. conn = JDBCUtil.getConnection();
  8. //开启事务
  9. conn.setAutoCommit(false);
  10. //2.定义sql
  11. String sql1="update login set money=money-? where name= ?";
  12. String sql2="update login set money=money+? where name= ?";
  13. //3.获取执行sql的对象
  14. pstmt1=conn.prepareStatement(sql1);
  15. pstmt2=conn.prepareStatement(sql2);
  16. //给?赋值
  17. pstmt1.setInt(1,500);
  18. pstmt1.setString(2,"大忽悠");
  19. pstmt2.setInt(1,500);
  20. pstmt2.setString(2,"小朋友");
  21. //4.执行
  22. pstmt1.executeUpdate();
  23. //异常的出现
  24. String s=null;
  25. s.getBytes(StandardCharsets.UTF_8);
  26. pstmt2.executeUpdate();
  27. //结束事务
  28. conn.commit();
  29. return true;
  30. } catch (SQLException throwables) {
  31. //事务进行回滚
  32. try {
  33. if(conn!=null)
  34. conn.rollback();
  35. } catch (SQLException e) {
  36. e.printStackTrace();
  37. }
  38. throwables.printStackTrace();
  39. }finally {
  40. JDBCUtil.close(pstmt1,conn);
  41. JDBCUtil.close(pstmt2,conn);
  42. }
  43. return false;
  44. }
使用NativeJdbcExtractor来获得"真相"

控制JdbcTemplate的行为

  1. protected void applyStatementSettings(Statement stmt) throws SQLException {
  2. int fetchSize = getFetchSize();
  3. if (fetchSize != -1) {
  4. stmt.setFetchSize(fetchSize);
  5. }
  6. int maxRows = getMaxRows();
  7. if (maxRows != -1) {
  8. stmt.setMaxRows(maxRows);
  9. }
  10. DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
  11. }

SQLException到DataAccessException体系的转义

  1. public interface SQLExceptionTranslator {
  2. @Nullable
  3. DataAccessException translate(String task, @Nullable String sql, SQLException ex);
  4. }

SQLErrorCodeSQLExceptionTranslator的doTranslate方法:

  1. @Override
  2. @Nullable
  3. protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) {
  4. SQLException sqlEx = ex;
  5. //首先对批处理异常进行处理
  6. if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {
  7. SQLException nestedSqlEx = sqlEx.getNextException();
  8. if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {
  9. sqlEx = nestedSqlEx;
  10. }
  11. }
  12. // First, try custom translation from overridden method.
  13. //首先尝试应用自定义异常翻译方法
  14. DataAccessException dae = customTranslate(task, sql, sqlEx);
  15. //如果返回值不为null,那就自定义异常翻译处理后的结果
  16. if (dae != null) {
  17. return dae;
  18. }
  19. // Next, try the custom SQLException translator, if available.
  20. //获取sql错误码集合
  21. SQLErrorCodes sqlErrorCodes = getSqlErrorCodes();
  22. if (sqlErrorCodes != null) {
  23. //尝试获取SQLErrorCodes中设置的自定义异常翻译器
  24. SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator();
  25. if (customTranslator != null) {
  26. //如果存在,就直接应用
  27. DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
  28. if (customDex != null) {
  29. return customDex;
  30. }
  31. }
  32. }
  33. // Check SQLErrorCodes with corresponding error code, if available.
  34. if (sqlErrorCodes != null) {
  35. String errorCode;
  36. //使用SqlState来进行异常转义
  37. if (sqlErrorCodes.isUseSqlStateForTranslation()) {
  38. errorCode = sqlEx.getSQLState();
  39. }
  40. else {
  41. // Try to find SQLException with actual error code, looping through the causes.
  42. // E.g. applicable to java.sql.DataTruncation as of JDK 1.6.
  43. SQLException current = sqlEx;
  44. while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) {
  45. current = (SQLException) current.getCause();
  46. }
  47. errorCode = Integer.toString(current.getErrorCode());
  48. }
  49. if (errorCode != null) {
  50. // Look for defined custom translations first.
  51. CustomSQLErrorCodesTranslation[] customTranslations = sqlErrorCodes.getCustomTranslations();
  52. if (customTranslations != null) {
  53. for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
  54. if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&
  55. customTranslation.getExceptionClass() != null) {
  56. DataAccessException customException = createCustomException(
  57. task, sql, sqlEx, customTranslation.getExceptionClass());
  58. if (customException != null) {
  59. logTranslation(task, sql, sqlEx, true);
  60. return customException;
  61. }
  62. }
  63. }
  64. }
  65. // Next, look for grouped error codes.
  66. if (Arrays.binarySearch(sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
  67. logTranslation(task, sql, sqlEx, false);
  68. return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
  69. }
  70. else if (Arrays.binarySearch(sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
  71. logTranslation(task, sql, sqlEx, false);
  72. return new InvalidResultSetAccessException(task, (sql != null ? sql : ""), sqlEx);
  73. }
  74. else if (Arrays.binarySearch(sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
  75. logTranslation(task, sql, sqlEx, false);
  76. return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
  77. }
  78. else if (Arrays.binarySearch(sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
  79. logTranslation(task, sql, sqlEx, false);
  80. return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
  81. }
  82. else if (Arrays.binarySearch(sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
  83. logTranslation(task, sql, sqlEx, false);
  84. return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
  85. }
  86. else if (Arrays.binarySearch(sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
  87. logTranslation(task, sql, sqlEx, false);
  88. return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
  89. }
  90. else if (Arrays.binarySearch(sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
  91. logTranslation(task, sql, sqlEx, false);
  92. return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
  93. }
  94. else if (Arrays.binarySearch(sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
  95. logTranslation(task, sql, sqlEx, false);
  96. return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
  97. }
  98. else if (Arrays.binarySearch(sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
  99. logTranslation(task, sql, sqlEx, false);
  100. return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
  101. }
  102. else if (Arrays.binarySearch(sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
  103. logTranslation(task, sql, sqlEx, false);
  104. return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
  105. }
  106. }
  107. }
  108. // We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.
  109. if (logger.isDebugEnabled()) {
  110. String codes;
  111. if (sqlErrorCodes != null && sqlErrorCodes.isUseSqlStateForTranslation()) {
  112. codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();
  113. }
  114. else {
  115. codes = "Error code '" + sqlEx.getErrorCode() + "'";
  116. }
  117. logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator");
  118. }
  119. return null;
  120. }
扩展SQLErrorCodeSQLExceptionTranslator完成自定义异常转义

  1. public class ToySQLExceptionTranslator extends SQLErrorCodeSQLExceptionTranslator {
  2. @Override
  3. protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
  4. if(sqlEx.getErrorCode()==123456){
  5. String msg = new StringBuilder()
  6. .append("出现未知错误,当执行")
  7. .append(task)
  8. .append(" with sql >")
  9. .append(sql)
  10. .toString();
  11. return new UnexpectedDataAccessException(msg,sqlEx);
  12. }
  13. return null;
  14. }
  15. }

提供sql-error-codes.xml自定义配置

  1. public class SQLErrorCodes {
  2. @Nullable
  3. private String[] databaseProductNames;
  4. private boolean useSqlStateForTranslation = false;
  5. private String[] badSqlGrammarCodes = new String[0];
  6. private String[] invalidResultSetAccessCodes = new String[0];
  7. private String[] duplicateKeyCodes = new String[0];
  8. private String[] dataIntegrityViolationCodes = new String[0];
  9. private String[] permissionDeniedCodes = new String[0];
  10. private String[] dataAccessResourceFailureCodes = new String[0];
  11. private String[] transientDataAccessResourceCodes = new String[0];
  12. private String[] cannotAcquireLockCodes = new String[0];
  13. private String[] deadlockLoserCodes = new String[0];
  14. private String[] cannotSerializeTransactionCodes = new String[0];
  15. ....

JDBCTemplate和它的兄弟们

使用JDBCTemplate进行数据访问
初始化JDBCTemplate

  1. public class TxMain {
  2. public static void main(String[] args) throws SQLException, ClassNotFoundException {
  3. DataSource dataSource = getDataSource();
  4. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  5. }
  6. private static DataSource getDataSource() {
  7. BasicDataSource basicDataSource = new BasicDataSource();
  8. YamlUtil yamlUtil = new YamlUtil("application.yml");
  9. basicDataSource.setDriverClassName(yamlUtil.get("spring.datasource.driver-class-name"));
  10. basicDataSource.setUrl(yamlUtil.get("spring.datasource.url"));
  11. basicDataSource.setUsername(yamlUtil.get("spring.datasource.username"));
  12. basicDataSource.setPassword(yamlUtil.get("spring.datasource.password"));
  13. return basicDataSource;
  14. }
  15. }

基于JDBCTemplate的数据访问

三种查询结果处理回调接口使用演示:

  1. public class TxMain {
  2. public static void main(String[] args) throws SQLException, ClassNotFoundException {
  3. DataSource dataSource = getDataSource();
  4. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  5. List<Shopper> shopperList = jdbcTemplate.query("select * from Salers", new ResultSetExtractor<List<Shopper>>() {
  6. @Override
  7. public List<Shopper> extractData(ResultSet rs) throws SQLException, DataAccessException {
  8. List<Shopper> res = new ArrayList<>();
  9. while (rs.next()) {
  10. Shopper shopper = Shopper.builder()
  11. .sno(rs.getString(1))
  12. .sname(rs.getString(2))
  13. .status(rs.getString(3))
  14. .city(rs.getString(4))
  15. .build();
  16. res.add(shopper);
  17. }
  18. return res;
  19. }
  20. });
  21. List<Shopper> shoppers = jdbcTemplate.query("select * from Salers", new RowMapper<Shopper>() {
  22. @Override
  23. public Shopper mapRow(ResultSet rs, int rowNum) throws SQLException {
  24. return Shopper.builder()
  25. .sno(rs.getString(1))
  26. .sname(rs.getString(2))
  27. .status(rs.getString(3))
  28. .city(rs.getString(4))
  29. .build();
  30. }
  31. });
  32. List<Shopper> res = new ArrayList<>();
  33. jdbcTemplate.query("select * from Salers", new RowCallbackHandler() {
  34. @Override
  35. public void processRow(ResultSet rs) throws SQLException {
  36. res.add(Shopper.builder()
  37. .sno(rs.getString(1))
  38. .sname(rs.getString(2))
  39. .status(rs.getString(3))
  40. .city(rs.getString(4))
  41. .build());
  42. }
  43. });
  44. }
  45. private static DataSource getDataSource() {
  46. BasicDataSource basicDataSource = new BasicDataSource();
  47. YamlUtil yamlUtil = new YamlUtil("application.yml");
  48. basicDataSource.setDriverClassName(yamlUtil.get("spring.datasource.driver-class-name"));
  49. basicDataSource.setUrl(yamlUtil.get("spring.datasource.url"));
  50. basicDataSource.setUsername(yamlUtil.get("spring.datasource.username"));
  51. basicDataSource.setPassword(yamlUtil.get("spring.datasource.password"));
  52. return basicDataSource;
  53. }
  54. }

基于JDBCTemplate的数据更新

  1. private static void updateCallBack(JdbcTemplate jdbcTemplate) {
  2. int count = jdbcTemplate.update("update Salers set SNAME= ? where SNO= ?", new PreparedStatementSetter() {
  3. @Override
  4. public void setValues(PreparedStatement ps) throws SQLException {
  5. ps.setString(1, "大忽悠集团");
  6. ps.setString(2, "1");
  7. }
  8. });
  9. System.out.println("影响的行数为: "+count);
  10. int acceptRows = jdbcTemplate.update(new PreparedStatementCreator() {
  11. @Override
  12. public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
  13. PreparedStatement ps = con.prepareStatement("update Salers set SNAME= ? where SNO= ?");
  14. ps.setString(1, "哈哈哈哈");
  15. ps.setString(2, "2");
  16. return ps;
  17. }
  18. });
  19. System.out.println("影响的行数为: "+acceptRows);
  20. }

  1. private static void dropTable(JdbcTemplate jdbcTemplate) {
  2. jdbcTemplate.execute("drop table temp");
  3. }
  4. private static void createTable(JdbcTemplate jdbcTemplate) {
  5. jdbcTemplate.execute("create table temp(name varchar(20) DEFAULT '无名氏',age int DEFAULT 18)");
  6. }

批量更新

批量更新演示:

  1. private static void batchUpdate(JdbcTemplate jdbcTemplate) {
  2. Shopper shopper1 = Shopper.builder().sno("123").sname("123").city("123").status("123").build();
  3. Shopper shopper2 = Shopper.builder().sno("1234").sname("1234").city("1234").status("1234").build();
  4. List<Shopper> shoppers = Arrays.asList(shopper1, shopper2);
  5. jdbcTemplate.batchUpdate("insert into Salers values (?,?,?,?)", new BatchPreparedStatementSetter() {
  6. @Override
  7. public void setValues(PreparedStatement ps, int i) throws SQLException {
  8. Shopper shopper = shoppers.get(i);
  9. ps.setString(1,shopper.sno);
  10. ps.setString(2,shopper.sname);
  11. ps.setString(3,shopper.status);
  12. ps.setString(4,shopper.city);
  13. }
  14. @Override
  15. public int getBatchSize() {
  16. return shoppers.size();
  17. }
  18. });
  19. }

调用存储过程

我们有下面这样一个存储过程:

  1. #创建一个存储过程,统计某个表中的记录个数
  2. DELIMITER $
  3. CREATE PROCEDURE CountTable(IN tableName VARCHAR(30),OUT sqlStr VARCHAR(60),OUT num INT)
  4. BEGIN
  5. SET @tableNum=0;
  6. SET @sqlSTR=CONCAT('select count(*) into @tableNum from ',tableName);
  7. PREPARE stmt FROM @sqlStr; #预定义sql
  8. EXECUTE stmt; #执行sql
  9. DEALLOCATE PREPARE stmt; #释放连接
  10. SET num=@tableNum;
  11. SET sqlStr=@sqlSTR;
  12. END $
  13. DELIMITER ;
  14. #测试
  15. SET @sqlStr="";
  16. SET @num=0;
  17. CALL CountTable("Salers",@sqlStr,@num);
  18. SELECT @sqlStr,@num;

使用JDBCTemplate完成上面存储过程的调用:

  1. private static void executeProcess(JdbcTemplate jdbcTemplate) {
  2. Map res = jdbcTemplate.execute("CALL CountTable(?,?,?)", new CallableStatementCallback<Map>() {
  3. @Override
  4. public Map doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
  5. //声明设置in/out参数
  6. cs.setString(1, "Salers");
  7. cs.registerOutParameter(2, Types.VARCHAR);
  8. cs.registerOutParameter(3, Types.INTEGER);
  9. //执行存储过程
  10. cs.execute();
  11. //抽取结果并返回
  12. Map map = new HashMap<>();
  13. map.put("SqlStr", cs.getString(2));
  14. map.put("count", cs.getInt(3));
  15. return map;
  16. }
  17. });
  18. System.out.println(res);
  19. }

递增主键生成策略抽象

  1. public interface DataFieldMaxValueIncrementer {
  2. int nextIntValue() throws DataAccessException;
  3. long nextLongValue() throws DataAccessException;
  4. String nextStringValue() throws DataAccessException;
  5. }

基于独立主键表的DataFieldMaxValueIncrementer

LAST_INSERT_ID()会返回最近执行的insert语句中由AUTO_INCREMENT生成的值。

注意一下几点:

  • 如果最近一条insert语句不涉及到AUTO_INCREMENT字段,也不会影响到LAST_INSERT_ID()的值。
  • insert需要成功插入数据,如果执行失败,LAST_INSERT_ID()不会改变。
  • insert语句如果指定了AUTO_INCREMENT字段的值,LAST_INSERT_ID()返回的值不会改变。
  • insert语句如果一次插入多行数据,则LAST_INSERT_ID()会返回插入的第一条数据的AUTO_INCREMENT值。
  • LAST_INSERT_ID()返回的值,在服务器端是以每个连接为基础保存的,也就是说,对于不同的连接,返回的值是不一样的。

下面我演示一下mysql相关的MySQLMaxValueIncrementer如何获取自增主键值

  1. private static void mySQLPrimaryKey(DataSource dataSource, JdbcTemplate jdbcTemplate) {
  2. MySQLMaxValueIncrementer incrementer = new MySQLMaxValueIncrementer
  3. (dataSource, "Products", "PNO");
  4. incrementer.setCacheSize(10);
  5. int update = jdbcTemplate.update("insert into Products values (?,?,?,?)", new Object[]{
  6. incrementer.nextLongValue(), "大忽悠", "蓝色", 20
  7. });
  8. System.out.println("accept rows = "+ update);
  9. }

getNextKey就是具体自增主键的获取和生成逻辑:

  1. @Override
  2. protected synchronized long getNextKey() throws DataAccessException {
  3. //maxID拿到的是PNO+10,而nextID为PNO+1,那么这就是缓存,知道maxID=nextID的时候,说明缓存用完了
  4. if (this.maxId == this.nextId) {
  5. ...
  6. // Increment the sequence column...
  7. String columnName = getColumnName();
  8. try {
  9. //关键点: 这里getIncrementerName()得到的就是Products,而columnName是PNO,cacheSize=10
  10. //因此完整语句就是UPDATE Products SET PNO = LAST_INSERT_ID(PNO + 10) LIMIT 1
  11. //该命令就是把当前最小的PNO值加10
  12. stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName +
  13. " = last_insert_id(" + columnName + " + " + getCacheSize() + ") limit 1");
  14. }
  15. catch (SQLException ex) {
  16. throw new DataAccessResourceFailureException("Could not increment " + columnName + " for " +
  17. getIncrementerName() + " sequence table", ex);
  18. }
  19. // Retrieve the new max of the sequence column...
  20. //VALUE_SQL为 "select last_insert_id()"---查询到最后一次修改得到的主键值为PNO+10
  21. ResultSet rs = stmt.executeQuery(VALUE_SQL);
  22. try {
  23. if (!rs.next()) {
  24. throw new DataAccessResourceFailureException("last_insert_id() failed after executing an update");
  25. }
  26. //拿到这个主键值
  27. this.maxId = rs.getLong(1);
  28. }
  29. finally {
  30. JdbcUtils.closeResultSet(rs);
  31. }
  32. //返回的主键id为查询到的PNO+10-10+1,即为PNO+1
  33. this.nextId = this.maxId - getCacheSize() + 1;
  34. }
  35. catch (SQLException ex) {
  36. throw new DataAccessResourceFailureException("Could not obtain last_insert_id()", ex);
  37. }
  38. finally {
  39. JdbcUtils.closeStatement(stmt);
  40. if (con != null) {
  41. if (this.useNewConnection) {
  42. try {
  43. con.commit();
  44. if (mustRestoreAutoCommit) {
  45. con.setAutoCommit(true);
  46. }
  47. }
  48. catch (SQLException ignore) {
  49. throw new DataAccessResourceFailureException(
  50. "Unable to commit new sequence value changes for " + getIncrementerName());
  51. }
  52. JdbcUtils.closeConnection(con);
  53. }
  54. else {
  55. DataSourceUtils.releaseConnection(con, getDataSource());
  56. }
  57. }
  58. }
  59. }
  60. else {
  61. //走缓存的情况
  62. this.nextId++;
  63. }
  64. return this.nextId;
  65. }

MySQLMaxValueIncrementer–Spring MySQL发号器介绍

基于Orcale的Sequence生成策略这里不多讲,感兴趣可以自行了解

Spring中的LOB类型处理

举个例子:

  • 首先准备一张数据库表
  1. create table `img` (
  2. `id` double ,
  3. `filename` varchar (600),
  4. `entity` longblob
  5. );

blob类型可能会因为图片太大而产生溢出,可以改为使用longblob

我们将该目录下面的所有图片保存到数据库中,然后再读取出来,将图片保存到另一个目录下

完整测试源码如下,测试是成功,大家可以自行拷贝尝试:

  1. public class BlobTestMain {
  2. private static final String INPUT_IMGS_DIR="C:\\Users\\zdh\\Desktop\\数据库实验截图\\实验四";
  3. private static final String OUTPUT_IMGS_DIR="C:\\Users\\zdh\\Desktop\\数据库实验截图\\testFile\\";
  4. public static void main(String[] args) throws SQLException, IOException {
  5. DataSource dataSource = getDataSource();
  6. Connection conn = dataSource.getConnection();
  7. //saveImgs(conn);
  8. readImgs(conn);
  9. }
  10. private static void readImgs(Connection conn) throws SQLException, IOException {
  11. Statement stmt = conn.createStatement();
  12. ResultSet rs = stmt.executeQuery("select fileName,entity from img");
  13. while(rs.next()){
  14. String imgName = rs.getString(1);
  15. InputStream imgOut = rs.getBinaryStream(2);
  16. readImg(imgName,imgOut);
  17. }
  18. close(conn, stmt, rs);
  19. }
  20. private static void readImg(String imgName,InputStream imgOut) throws IOException {
  21. BufferedInputStream imgBuf = new BufferedInputStream(imgOut);
  22. PrintStream imgOutPrint = new PrintStream(OUTPUT_IMGS_DIR+imgName);
  23. byte[] bytes = new byte[1024];
  24. int len=-1;
  25. while((len=imgBuf.read(bytes))!=-1){
  26. imgOutPrint.write(bytes,0,len);
  27. }
  28. imgOutPrint.close();
  29. imgBuf.close();
  30. }
  31. private static void close(Connection conn, Statement stmt, ResultSet rs) throws SQLException {
  32. if(rs !=null){
  33. rs.close();
  34. }
  35. if(stmt !=null){
  36. stmt.close();
  37. }
  38. if(conn !=null){
  39. conn.close();
  40. }
  41. }
  42. private static void saveImgs(Connection conn) throws SQLException {
  43. File imgs = getInputFile();
  44. List<FileInputStream> fileInputs = getFileInputs(imgs);
  45. File[] files = getFiles(imgs);
  46. PreparedStatement ps=null;
  47. for (int i = 0; i < fileInputs.size(); i++) {
  48. ps = conn.prepareStatement("insert into img(filename,entity) values(?,?)");
  49. ps.setString(1,files[i].getName());
  50. ps.setBinaryStream(2,fileInputs.get(i),(int)files[i].length());
  51. ps.executeUpdate();
  52. }
  53. close(conn, fileInputs, ps);
  54. }
  55. private static void close(Connection conn, List<FileInputStream> fileInputs, PreparedStatement ps) throws SQLException {
  56. if(ps !=null){
  57. ps.close();
  58. }
  59. if(conn !=null){
  60. conn.close();
  61. }
  62. fileInputs.forEach(fileInputStream -> {
  63. try {
  64. fileInputStream.close();
  65. } catch (IOException e) {
  66. e.printStackTrace();
  67. }
  68. });
  69. }
  70. private static File[] getFiles(File imgs) {
  71. return imgs.listFiles();
  72. }
  73. private static List<FileInputStream> getFileInputs(File imgs) {
  74. return Arrays.stream(imgs.listFiles()).map(file -> {
  75. try {
  76. return new FileInputStream(file);
  77. } catch (FileNotFoundException e) {
  78. e.printStackTrace();
  79. }
  80. return null;
  81. }).collect(Collectors.toList());
  82. }
  83. private static File getInputFile() {
  84. File imgs = new File(INPUT_IMGS_DIR);
  85. return imgs;
  86. }
  87. private static DataSource getDataSource() {
  88. BasicDataSource basicDataSource = new BasicDataSource();
  89. YamlUtil yamlUtil = new YamlUtil("application.yml");
  90. basicDataSource.setDriverClassName(yamlUtil.get("spring.datasource.driver-class-name"));
  91. basicDataSource.setUrl(yamlUtil.get("spring.datasource.url"));
  92. basicDataSource.setUsername(yamlUtil.get("spring.datasource.username"));
  93. basicDataSource.setPassword(yamlUtil.get("spring.datasource.password"));
  94. return basicDataSource;
  95. }
  96. }

Oracle数据库的测试大家就自行完成吧,这里不再给出具体案例了

LobHandler

  1. public interface LobHandler {
  2. @Nullable
  3. byte[] getBlobAsBytes(ResultSet rs, String columnName) throws SQLException;
  4. @Nullable
  5. byte[] getBlobAsBytes(ResultSet rs, int columnIndex) throws SQLException;
  6. @Nullable
  7. InputStream getBlobAsBinaryStream(ResultSet rs, String columnName) throws SQLException;
  8. @Nullable
  9. InputStream getBlobAsBinaryStream(ResultSet rs, int columnIndex) throws SQLException;
  10. @Nullable
  11. String getClobAsString(ResultSet rs, String columnName) throws SQLException;
  12. @Nullable
  13. String getClobAsString(ResultSet rs, int columnIndex) throws SQLException;
  14. @Nullable
  15. InputStream getClobAsAsciiStream(ResultSet rs, String columnName) throws SQLException;
  16. @Nullable
  17. InputStream getClobAsAsciiStream(ResultSet rs, int columnIndex) throws SQLException;
  18. Reader getClobAsCharacterStream(ResultSet rs, String columnName) throws SQLException;
  19. Reader getClobAsCharacterStream(ResultSet rs, int columnIndex) throws SQLException;
  20. LobCreator getLobCreator();
  21. }

数据插入:

  1. DefaultLobHandler lobHandler = new DefaultLobHandler();
  2. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  3. jdbcTemplate.execute("insert into img(filename,entity) values(?,?)",
  4. new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
  5. @SneakyThrows
  6. @Override
  7. protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
  8. ps.setString(1,"大忽悠的图片.png");
  9. lobCreator.setBlobAsBinaryStream(ps,2,new FileInputStream(INPUT_IMG),(int)new File(INPUT_IMG).length());
  10. }
  11. });

数据访问:

  1. final DefaultLobHandler lobHandler = new DefaultLobHandler();
  2. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  3. InputStream ins = (InputStream) jdbcTemplate.queryForObject("select entity from img where id=1", new RowMapper() {
  4. @Override
  5. public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
  6. return lobHandler.getBlobAsBinaryStream(rs, 1);
  7. }
  8. });

拿到input流,后面做啥,我就不多说了

  1. final DefaultLobHandler lobHandler = new DefaultLobHandler();
  2. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  3. jdbcTemplate.query("select entity from img where id=1",new AbstractLobStreamingResultSetExtractor() {
  4. @Override
  5. protected void streamData(ResultSet rs) throws SQLException, IOException, DataAccessException {
  6. InputStream ins = lobHandler.getBlobAsBinaryStream(rs, 1);
  7. }
  8. });
NamedParameterJdbcTemplate

  1. DataSource dataSource = getDataSource();
  2. NamedParameterJdbcTemplate npJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
  3. MapSqlParameterSource parameterSource = new MapSqlParameterSource("filename", "任务19.png");
  4. List<Integer> res = npJdbcTemplate.query("select count(*) from img where filename=:filename", parameterSource, new RowMapper<Integer>() {
  5. @Override
  6. public Integer mapRow(ResultSet rs, int rowNum) throws SQLException {
  7. return rs.getInt(1);
  8. }
  9. });
  10. System.out.println(res);

SimpleJdbcCTemplate

创作打卡挑战赛

赢取流量/现金/CSDN周边激励大奖

相关文章