db操做能夠說是java後端的必備技能了,實際項目中,直接使用JdbcTemplate的機會並很少,大可能是mybatis,hibernate,jpa或者是jooq,而後前幾天寫一個項目,由於db操做很是簡單,就直接使用JdbcTemplate,然而悲催的發現,對他的操做並無預期中的那麼順暢,因此有必要好好的學一下JdbcTemplate的CURD;本文爲第一篇,插入數據java
<!-- more -->mysql
使用SpringBoot進行db操做引入幾個依賴,就能夠愉快的玩耍了,這裏的db使用mysql,對應的pom依賴如git
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> </dependencies>
接着就是db的配置信息,下面是鏈接我本機的數據庫配置github
## DataSource spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false spring.datasource.driver-class-name= com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=
建立一個測試dbspring
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '錢', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
直接引入jdbcTemplate,注入便可,不須要其餘的操做sql
@Autowired private JdbcTemplate jdbcTemplate;
直接寫完整的插入sql,這種方式比較簡單粗暴數據庫
private boolean insertBySql() { // 簡單的sql執行 String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES ('一灰灰blog', 100, 0);"; return jdbcTemplate.update(sql) > 0; }
這種插入方式中,sql使用佔位符?,而後插入值經過參數傳入便可後端
private boolean insertBySqlParams() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; return jdbcTemplate.update(sql, "一灰灰2", 200, 0) > 0; }
經過Statement能夠指定參數類型,這種插入方式更加安全,有兩種常見的方式,注意設置參數時,起始值爲1,而不是一般說的0數組
private boolean insertByStatement() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; return jdbcTemplate.update(sql, new PreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement) throws SQLException { preparedStatement.setString(1, "一灰灰3"); preparedStatement.setInt(2, 300); byte b = 0; preparedStatement.setByte(3, b); } }) > 0; } private boolean insertByStatement2() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; return jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, "一灰灰4"); preparedStatement.setInt(2, 400); byte b = 0; preparedStatement.setByte(3, b); return preparedStatement; } }) > 0; }
這個屬於比較常見的需求了,我但願獲取插入數據的主鍵id,用於後續的業務使用; 這時就須要用KeyHolder
安全
/** * 新增數據,並返回主鍵id * * @return */ private int insertAndReturnId() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { // 指定主鍵 PreparedStatement preparedStatement = connection.prepareStatement(sql, new String[]{"id"}); preparedStatement.setString(1, "一灰灰5"); preparedStatement.setInt(2, 500); byte b = 0; preparedStatement.setByte(3, b); return preparedStatement; } }, keyHolder); return keyHolder.getKey().intValue(); }
看上面的實現,和前面差很少,可是有一行須要額外注意, 在獲取Statement時,須要制定主鍵,不然會報錯
// 指定主鍵 PreparedStatement preparedStatement = connection.prepareStatement(sql, new String[]{"id"});
基本插入看完以後,再看批量插入,會發現和前面的姿式沒有太大的區別,無非是傳入一個數組罷了,以下面的幾種使用姿式
private void batchInsertBySql() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES " + "('Batch 一灰灰blog', 100, 0), ('Batch 一灰灰blog 2', 100, 0);"; int[] ans = jdbcTemplate.batchUpdate(sql); System.out.println("batch insert by sql: " + JSON.toJSONString(ans)); } private void batchInsertByParams() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; Object[] param1 = new Object[]{"Batch 一灰灰 3", 200, 0}; Object[] param2 = new Object[]{"Batch 一灰灰 4", 200, 0}; int[] ans = jdbcTemplate.batchUpdate(sql, Arrays.asList(param1, param2)); System.out.println("batch insert by params: " + JSON.toJSONString(ans)); } private void batchInsertByStatement() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; int[] ans = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { if (i == 0) { preparedStatement.setString(1, "batch 一灰灰5"); } else { preparedStatement.setString(1, "batch 一灰灰6"); } preparedStatement.setInt(2, 300); byte b = 0; preparedStatement.setByte(3, b); } @Override public int getBatchSize() { return 2; } }); System.out.println("batch insert by statement: " + JSON.toJSONString(ans)); }
接下來咱們測試下上面的代碼執行狀況
@Component public class InsertService { /** * 簡單的新增一條數據 */ public void basicInsert() { System.out.println("basic insert: " + insertBySql()); System.out.println("insertBySqlParams: " + insertBySqlParams()); System.out.println("insertByStatement: " + insertByStatement()); System.out.println("insertByStatement2: " + insertByStatement2()); System.out.println("insertAndReturn: " + insertAndReturnId()); List<Map<String, Object>> result = jdbcTemplate.queryForList("select * from money"); System.out.println("after insert, the records:\n" + result); } /** * 批量插入數據 */ public void batchInsert() { batchInsertBySql(); batchInsertByParams(); batchInsertByStatement(); } } @SpringBootApplication public class Application { public Application(InsertService insertService) { insertService.basicInsert(); insertService.batchInsert(); } public static void main(String[] args) { SpringApplication.run(Application.class); } }
輸出結果如
basic insert: true insertBySqlParams: true insertByStatement: true insertByStatement2: true insertAndReturn: 5 after insert, the records: [{id=1, name=一灰灰blog, money=100, is_deleted=false, create_at=2019-04-08 10:22:50.0, update_at=2019-04-08 10:22:50.0}, {id=2, name=一灰灰2, money=200, is_deleted=false, create_at=2019-04-08 10:22:55.0, update_at=2019-04-08 10:22:55.0}, {id=3, name=一灰灰3, money=300, is_deleted=false, create_at=2019-04-08 10:22:55.0, update_at=2019-04-08 10:22:55.0}, {id=4, name=一灰灰4, money=400, is_deleted=false, create_at=2019-04-08 10:22:55.0, update_at=2019-04-08 10:22:55.0}, {id=5, name=一灰灰5, money=500, is_deleted=false, create_at=2019-04-08 10:22:55.0, update_at=2019-04-08 10:22:55.0}] batch insert by sql: [2] batch insert by params: [1,1] batch insert by statement: [1,1]
上面還漏了一個批量插入時,也須要返回主鍵id,改怎麼辦?
直接看JdbcTemplate的接口,並無發現相似單個插入獲取主鍵的方式,是否是意味着無法實現呢?
固然不是了,既然沒有提供,咱們徹底能夠依葫蘆畫瓢,本身實現一個 ExtendJdbcTemplate
, 首先看先單個插入返回id的實現如
接下來,咱們本身的實現能夠以下
public class ExtendJdbcTemplate extends JdbcTemplate { public ExtendJdbcTemplate(DataSource dataSource) { super(dataSource); } public int[] batchUpdate(final String sql, final BatchPreparedStatementSetter pss, final KeyHolder generatedKeyHolder) throws DataAccessException { return execute(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); } }, ps -> { try { int batchSize = pss.getBatchSize(); int totalRowsAffected = 0; int[] rowsAffected = new int[batchSize]; List generatedKeys = generatedKeyHolder.getKeyList(); generatedKeys.clear(); ResultSet keys = null; for (int i = 0; i < batchSize; i++) { pss.setValues(ps, i); rowsAffected[i] = ps.executeUpdate(); totalRowsAffected += rowsAffected[i]; try { keys = ps.getGeneratedKeys(); if (keys != null) { RowMapper rowMapper = new ColumnMapRowMapper(); RowMapperResultSetExtractor rse = new RowMapperResultSetExtractor(rowMapper, 1); generatedKeys.addAll(rse.extractData(keys)); } } finally { JdbcUtils.closeResultSet(keys); } } if (logger.isDebugEnabled()) { logger.debug("SQL batch update affected " + totalRowsAffected + " rows and returned " + generatedKeys.size() + " keys"); } return rowsAffected; } finally { if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } }); } }
封裝完畢以後,咱們的使用姿式能夠爲
@Autowired private ExtendJdbcTemplate extendJdbcTemplate; private void batchInsertAndReturnId() { String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);"; GeneratedKeyHolder generatedKeyHolder = new GeneratedKeyHolder(); extendJdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { if (i == 0) { preparedStatement.setString(1, "batch 一灰灰7"); } else { preparedStatement.setString(1, "batch 一灰灰8"); } preparedStatement.setInt(2, 400); byte b = 0; preparedStatement.setByte(3, b); } @Override public int getBatchSize() { return 2; } }, generatedKeyHolder); System.out.println("batch insert and return id "); List<Map<String, Object>> objectMap = generatedKeyHolder.getKeyList(); for (Map<String, Object> map : objectMap) { System.out.println(map.get("GENERATED_KEY")); } }
而後測試執行,輸出結果以下
本篇主要介紹使用JdbcTemplate插入數據的幾種經常使用姿式,分別從單個插入和批量插入進行了實例演示,包括如下幾種常見姿式
update(sql)
update(sql, param1, param2...)
update(sql, new PreparedStatementCreator(){})
update(new PreparedStatementSetter(){})
update(new PreparedStatementCreator(){}, new GeneratedKeyHolder())
批量插入姿式和上面差很少,惟一須要注意的是,若是你想使用批量插入,並獲取主鍵id,目前我沒有找到能夠直接使用的接口,若是有這方面的需求,能夠參考下我上面的使用姿式
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激