JdbcTemplate的用法


Spring Boot JdbcTemplate 入門

​ 在實際項目中,在對數據庫訪問層對數據庫進行操作時,大部分時候我們都用的mybatis,但是偶爾會有用到使用jdbc的時候,一般使用jdbc的話要么就自己封裝一個jdbc連接池進行使用,要么就是使用Apache Common 提供的 DbUtils 工具類,還有個就是Spring JDBC ,提供的JdbcTemplate 工具類

因為我的工作中基本上都是使用的spring boot,所以就學習了一下JdbcTemplate 工具類的使用

JdbcTemplate是Spring對JDBC的封裝,目的是使JDBC更加易於使用。JdbcTemplate是Spring的一部分。JdbcTemplate處理了資源的建立和釋放。他幫助我們避免一些常見的錯誤,比如忘了總要關閉連接。他運行核心的JDBC工作流,如Statement的建立和執行,而我們只需要提供SQL語句和提取結果。

在JdbcTemplate中執行SQL語句的方法大致分為3類:

  1. execute:可以執行所有SQL語句,一般用於執行DDL語句。
  2. update:用於執行INSERTUPDATEDELETE等DML語句。
  3. queryXxx:用於DQL數據查詢語句。
  4. call:用於執行存儲過程、函數相關語句。

應用場景以及技術選型

首先先說一下JdbcTemplate 的應用場景,其他的應用場景我說不准,現在java項目中大多數的數據訪問層用的都是mybatis,但是在項目中總會遇到那種數據的批量插入,或者批量刪除等操作,在這樣的場景下,總要考慮到一個性能的問題,所以在做之前總要先行做好技術選型,所以這里就拿Mybatis和傳統的Jdbc做了一下比較。

這里我先用JdbcTemplate 和mybatis分別做一下批量數據插入的測試:

首先環境准備,測試是在相同變量的情況下進行的,所以我就使用同一個mysql5.7的數據庫進行測試。

先建表,建表可看DDL操作那里,這里先根據表結構,創建一個實體類:

TestRecord.java

public class TestRecord {
	private String aa;
	private String bb;
	private String cc;
	private String dd;
	private String ee;
	private String ff;
	private String gg;
	private String hh;
	private Integer ii;
	private Integer jj;
//getter   and   setter
}

JdbcTemplate

代碼寫法后面講解,

案例代碼:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class JdbcDMLTest {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	String sql="insert into test_record(a,b, c, d, e, f, g, h, i,j) " +
			"   values(?,?,?,?,?,?,?,?,?,?) ";
	//預編譯的批量插入
	@Test
	public  void  batchUpdate(){
		//獲取參數
		List<TestRecord> recordList=new ArrayList<>();
		for(int i=0;i<1000;i++){
			TestRecord testRecord=new TestRecord();
			testRecord.setAa("aa"+i);
			testRecord.setBb("bb"+i);
			testRecord.setCc("cc"+i);
			testRecord.setDd("dd"+i);
			testRecord.setEe("ee"+i);
			testRecord.setFf("ff"+i);
			testRecord.setGg("gg"+i);
			testRecord.setHh("hh"+i);
			testRecord.setIi(i);
			testRecord.setJj(i);
			recordList.add(testRecord);
		}

		Long start=System.currentTimeMillis();
		int[] count = jdbcTemplate.batchUpdate(sql,  new BatchPreparedStatementSetter() {

			@Override
			public void setValues(PreparedStatement ps, int i)
					throws SQLException {
				//注入參數值
				ps.setString(1, recordList.get(i).getAa());
				ps.setString(2 ,recordList.get(i).getBb());
				ps.setString(3, recordList.get(i).getCc());
				ps.setString(4, recordList.get(i).getDd());
				ps.setString(5, recordList.get(i).getEe());
				ps.setString(6, recordList.get(i).getFf());
				ps.setString(7, recordList.get(i).getGg());
				ps.setString(8, recordList.get(i).getHh());
				ps.setInt(9,recordList.get(i).getIi() );
				ps.setInt(10, recordList.get(i).getJj());
			}

			@Override
			public int getBatchSize() {
				//批量執行的數量
				return recordList.size();
			}
		});
		System.out.println("插入數量:"+count.length+",用時:"+(System.currentTimeMillis()-start));
	}
}    

在執行代碼之前需要注意一個點,JDBC做批量插入的時候需要在mysql的驅動配置信息里加一個屬性配置,這個是開啟批量執行的關鍵

rewriteBatchedStatements=true   //加在配置文件數據庫url參數中

執行程序:

可以看到1000條數據是549毫秒,也就是0.5秒完成插入。下來看一下mybatis的處理效率

Mybatis

案例代碼:

MybatisBatchDML.java

package com.zkk.test.javaTest;

import com.zkk.test.TestApplication;
import com.zkk.test.domain.TestRecord;
import com.zkk.test.mapper.BatchInertTestMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * description
 *
 * @author Zaker 2020/06/04 17:21
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class MybatisBatchDML {
	@Autowired
	private BatchInertTestMapper batchInertTestMapper;

	@Test
	public  void  mybatisBatchInsert(){
		List<TestRecord> recordList=new ArrayList<>();
		for(int i=0;i<1000;i++){
			TestRecord testRecord=new TestRecord();
			testRecord.setAa("aa"+i);
			testRecord.setBb("bb"+i);
			testRecord.setCc("cc"+i);
			testRecord.setDd("dd"+i);
			testRecord.setEe("ee"+i);
			testRecord.setFf("ff"+i);
			testRecord.setGg("gg"+i);
			testRecord.setHh("hh"+i);
			testRecord.setIi(i);
			testRecord.setJj(i);
			recordList.add(testRecord);
		}
		Map<String,Object> param =new HashMap();
		param.put("recordList", recordList);
		Long start=System.currentTimeMillis();
		int count=batchInertTestMapper.batchInsert(param);
		System.out.println("插入數量:"+count+",用時:"+(System.currentTimeMillis()-start));
	}
}

BatchInertTestMapper.java

package com.zkk.test.mapper;


import com.zkk.test.domain.TestRecord;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;


/**
 * description
 *
 * @author Zaker 2020/06/04 17:32
 */

@Repository
@Mapper
public interface BatchInertTestMapper {
	Integer batchInsert(Map<String,Object> param);
}

BatchInertTestMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.zkk.test.mapper.BatchInertTestMapper">

    <insert id="batchInsert" parameterType="java.util.Map">
        insert into test_record
        (a,b,c,d,e,f,g,h,i,j)
        values
        <foreach collection="recordList" item="record" open="" close=""
                 separator=",">
            (
            #{record.aa},
            #{record.bb},
            #{record.cc},
            #{record.dd},
            #{record.ee},
            #{record.ff},
            #{record.gg},
            #{record.hh},
            #{record.ii},
            #{record.jj}
            )
        </foreach>
        ;
    </insert>
</mapper>

以上就是一套很常見的mybatis使用foreach處理的批量數據插入的寫法

注:這里面mybatis使用的都是拼接字符串的形式然后再執行,所必須注意mybatis中拼接的字符串的長度的限制,如果數據量過大,需要分批進行執行。

執行結果:

可以看到使用mybatis插入1000條數據使用了1764毫秒,就是1.7秒,和JDBC的差距已經展現出來了。

mybatis也使用的批量執行,那如果就單純的for循環逐條執行插入呢?這里我就不嘗試了,隨便想想就能想到只會更慢,更別說更大批量的數據了。

對比結果

下來兩種方式我做了一個對比測試,這種測試不一定每次結果都是一樣的,所以就做5次測試,選擇中間的一個值做對比(懶得算平均值)

記錄數 JDBC耗時/ms Mybatis耗時/ms
100 432 727
1000 538 1340
10000 1036 3816
100000 5368 14937

可以看到這里的執行效果差,明顯JDBC在處理這種大批量的數據的時候表現是更加優秀的,所以在有這種處理大批量數據的場景的時候,可以考慮選擇JDBC來進行實現。

詳細介紹

JdbcTemplate類對可變部分采用回調接口方式實現,如ConnectionCallback通過回調接口返回給用戶一個連接,從而可以使用該連接做任何事情、StatementCallback通過回調接口返回給用戶一個Statement,從而可以使用該Statement做任何事情等等,還有其他一些回調接口

DDL操作

主要使用execute:用於執行DDL語句,這里嘗試一下使用JdbcTemplate 做DDL操作,新建一張表

案例代碼:

package com.zkk.test.javaTest;

import com.zkk.test.TestApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * description
 *
 * @author Zaker 2020/06/04 14:26
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
public class JdbcDDL {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Test
	public  void caret() {

		// 創建表的SQL語句
		String sql = "CREATE TABLE test_record("
				+ "a  VARCHAR(20),"
				+ "b  VARCHAR(20),"
				+ "c  VARCHAR(20),"
				+ "d  VARCHAR(20),"
				+ "e  VARCHAR(20),"
				+ "f  VARCHAR(20),"
				+ "g  VARCHAR(20),"
				+ "h  VARCHAR(20),"
				+ "i   int,"
				+ "j   int"
				+ ");";
		jdbcTemplate.execute(sql);
	}
}

執行測試用例,執行完成沒有報錯,查看數據庫可以看到創建成功

這樣一個簡單的建表操作就完成了。

具體的其他的一些刪除以及更改等操作就不做練習了,大概會用就行。

DML操作

單條數據操作

一般都是使用update方法,主要看一下update有哪些方法

  • int update(String sql)

    用來執行固定的sql,沒有參數注入,直接執行,因為沒有預編譯,所以應該是效率會慢。

  • int update(String sql, Object... args)

    執行參數注入的sql,Object... args可變參數組里面放入的是要注入的參數值

  • int update(PreparedStatementCreator psc)

    PreparedStatementCreator ,通過回調獲取JdbcTemplate提供的Connection,由用戶使用該Conncetion創建相關的preparedStatement預編譯對象,使用原始jdbc方式給預編譯sql注入參數

  • int update(String sql, PreparedStatementSetter pss)

    可以進行參數注入,PreparedStatementSetter 方法就是用來獲取預編譯語句對象,並設置相應參數值。

  • int update(String sql, Object... args,int[] argTypes)

    執行參數注入的sql,Object... args可變參數組里面放入的是要注入的參數值,argTypes里面是需要注入的sql參數的JDBC類型(java.sql.Types中來獲取類型的常量

  • int update(PreparedStatementCreator psc,KeyHolder generatedKeyHolder)

    如果需要返回插入的主鍵,只能用此方法,增加KeyHolder參數

案例1:int update(String sql)

@Test
public void  insertOne1(){

    String sql="insert into test_record(a,b, c, d, e, f, g, h, i,j)  " +
        "values(a,b,c,d,e,f,g,h,1,2)";
    //返回的是更新的行數
    int count = jdbcTemplate.update(sql);
    System.out.println("影響的行數:"+count);

}

案例2:int update(String sql, Object... args)

@Test
public void  insertOne3(){
    String sql="insert into test_record(a,b, c, d, e, f, g, h, i,j) " +
        "   values(?,?,?,?,?,?,?,?,?,?) ";
    //返回的是更新的行數
    int count = jdbcTemplate.update(sql,new Object[]{"a","b","c","d","e","f","g","h",1,1});
    System.out.println("影響的行數:"+count);

}

案例3

增加、更新、刪除(一條sql語句)(sql需要參數注入)

int update(String sql, PreparedStatementSetter pss)

String sql="insert into test_record(a,b, c, d, e, f, g, h, i,j) " +
			"   values(?,?,?,?,?,?,?,?,?,?) ";

@Test
public void  insertOne(){
    //返回的是更新的行數
    int count = jdbcTemplate.update(sql, new PreparedStatementSetter(){
        @Override
        public void setValues(PreparedStatement pstmt)
            throws SQLException {
            pstmt.setObject(1, "aa");
            pstmt.setObject(2 ,"bb");
            pstmt.setObject(3, "cc");
            pstmt.setObject(4, "dd");
            pstmt.setObject(5, "ee");
            pstmt.setObject(6, "ff");
            pstmt.setObject(7, "gg");
            pstmt.setObject(8, "hh");
            pstmt.setObject(9, 1);
            pstmt.setObject(10, 1);
        }
    });
    System.out.println("影響的行數:"+count);
}

執行結果:成功

查看數據庫:成功

批量數據操作

案例1:批量插入(需要預編譯的)

批量操作的話使用的是batchUpdate這個方法,這個方法有很多不同的實現,可以看一下

第一個方法int[] batchUpdate(String sql, BatchPreparedStatementSetter pss)就是前面應用場景案例那里的方法,可以看一下大概的用法。

值得注意的是BatchPreparedStatementSetter 這個方法類似於PreparedStatementSetter,但用於批處理,需要指定批處理大小。

下面再練習一下

案例2: 批量插入(需要預編譯的)

int[] batchUpdate(String sql,List<Object[]> batchArgs)

//預編譯的批量插入2
	@Test
	public  void  batchUpdate2(){
		List<Object[]> batchArgs=new ArrayList<Object[]>();
		//獲取參數
		List<TestRecord> recordList=new ArrayList<>();
		for(int i=0;i<10000;i++){
			TestRecord testRecord=new TestRecord();
			testRecord.setAa("aa"+i);
			testRecord.setBb("bb"+i);
			testRecord.setCc("cc"+i);
			testRecord.setDd("dd"+i);
			testRecord.setEe("ee"+i);
			testRecord.setFf("ff"+i);
			testRecord.setGg("gg"+i);
			testRecord.setHh("hh"+i);
			testRecord.setIi(i);
			testRecord.setJj(i);
			recordList.add(testRecord);
		}
		for(TestRecord record:recordList){
			batchArgs.add(new Object[]{record.getAa(),
					record.getBb(),
					record.getCc(),
					record.getDd(),
					record.getEe(),
					record.getFf(),
					record.getGg(),
					record.getHh(),
					record.getIi(),
					record.getJj()});
		}
		Long start=System.currentTimeMillis();
		int[] count = jdbcTemplate.batchUpdate(sql,batchArgs);
		System.out.println("插入數量:"+count.length+",用時:"+(System.currentTimeMillis()-start));
	}

這里就是把預編譯用的參數以數組的形式放入,比上一種方式操作起來更好理解。

查看執行結果:

一萬條據也只用了1429毫秒,也就是1.4秒。

這里只做兩個常用方法的練習。

DQL操作

查詢語句的話可以做一個了解,就不做demo練習了

public int queryForInt(String sql) 執行查詢語句,返回一個int類型的值。

public long queryForLong(String sql) 執行查詢語句,返回一個Long類型的數據。

public T queryForObject(String sql, Class requiredType) 執行查詢語句,返回一個指定類型的數據。

public Map<String, Object> queryForMap(String sql) 執行查詢語句,將一條記錄放到一個Map中。

public List<Map<String, Object>> queryForList(String sql) 執行查詢語句,返回一個List集合,List中存放的是Map類型的數據。

public List query(String sql, RowMapper rowMapper) 執行查詢語句,返回一個List集合,List中存放的是RowMapper指定類型的數據。

public List query(String sql, RowMapper rowMapper) 執行查詢語句,返回一個List集合,List中存放的是RowMapper指定類型的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM