SpringBoot高級篇JdbcTemplate之數據插入使用姿式詳解

db操做能夠說是java後端的必備技能了,實際項目中,直接使用JdbcTemplate的機會並很少,大可能是mybatis,hibernate,jpa或者是jooq,而後前幾天寫一個項目,由於db操做很是簡單,就直接使用JdbcTemplate,然而悲催的發現,對他的操做並無預期中的那麼順暢,因此有必要好好的學一下JdbcTemplate的CURD;本文爲第一篇,插入數據java

<!-- more -->mysql

I. 環境

1. 配置相關

使用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=

2. 測試db

建立一個測試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;

II. 使用姿式

直接引入jdbcTemplate,注入便可,不須要其餘的操做sql

@Autowired
private JdbcTemplate jdbcTemplate;

1. sql直接插入一條數據

直接寫完整的插入sql,這種方式比較簡單粗暴數據庫

private boolean insertBySql() {
    // 簡單的sql執行
    String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES ('一灰灰blog', 100, 0);";
    return jdbcTemplate.update(sql) > 0;
}

2. 參數替換方式插入

這種插入方式中,sql使用佔位符?,而後插入值經過參數傳入便可後端

private boolean insertBySqlParams() {
    String sql = "INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (?, ?, ?);";
    return jdbcTemplate.update(sql, "一灰灰2", 200, 0) > 0;
}

3. 經過Statement方式插入

經過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;
}

4. 插入並返回主鍵id

這個屬於比較常見的需求了,我但願獲取插入數據的主鍵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"});

5. 批量插入

基本插入看完以後,再看批量插入,會發現和前面的姿式沒有太大的區別,無非是傳入一個數組罷了,以下面的幾種使用姿式

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));
}

6. 測試

接下來咱們測試下上面的代碼執行狀況

@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]

執行結果

II. 擴展

1. 批量插入並返回主鍵id

上面還漏了一個批量插入時,也須要返回主鍵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"));
    }
}

而後測試執行,輸出結果以下

批量插入返回id

2. 小結

本篇主要介紹使用JdbcTemplate插入數據的幾種經常使用姿式,分別從單個插入和批量插入進行了實例演示,包括如下幾種常見姿式

  • update(sql)
  • update(sql, param1, param2...)
  • update(sql, new PreparedStatementCreator(){})
  • update(new PreparedStatementSetter(){})
  • update(new PreparedStatementCreator(){}, new GeneratedKeyHolder())

批量插入姿式和上面差很少,惟一須要注意的是,若是你想使用批量插入,並獲取主鍵id,目前我沒有找到能夠直接使用的接口,若是有這方面的需求,能夠參考下我上面的使用姿式

IV. 其餘

2. 聲明

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

QrCode

相關文章
相關標籤/搜索