JDBCTemplate

JDBCTemplate

2021/06/27 (重整內容)

使用JdbcTemplate讀取資料

利用Spring Framework提供的JdbcTemplate執行SQL Statement,可以大量的簡化所需要的程式碼。常用的有方法有update、query及queryForObject。

jdbcTemplate.update的第一個參數是SQL Statement,在SQL裡會有"?"來接受參數,接下來的參數就是依順序傳到前面的"?"。這樣的寫法跟我們在使用php的寫法很像。

public int insert(Customer customer) {

return jdbcTemplate.update(

"insert into customer (name, address, weight) values(?, ?, ?)",

customer.getName(), customer.getAddress(), customer.getWeight());

}

public int update(Customer customer) {

return jdbcTemplate.update(

"update customer set name=?, address=?, weight=? where id =?",

customer.getName(), customer.getAddress(), customer.getWeight(), customer.getId());

}


public int delete(Long id) {

return jdbcTemplate.update(

"delete from customer where id =?", id);

}

jdbcTemplate.query或queryForObject的第一個參數是SQL Statement,第二個可省略的參數是傳給statement的參數,接下來的參數就是接收結果的物件。

this.jdbcTemplate.queryForObject(

"select id, name, address, weight from customer where id = ?",

new Object[]{id}, new CustomerMapper());

省略第二個參數

this.jdbcTemplate.query( "select id, name, address, weight from customer", new CustomerMapper());

使用RowMapper來產生接收結果的物件,mapRow會收到兩個參數:rs跟rowNum,利用rs取得欄位內容。例如:取得欄位"id",資料的內容是個Long (integer)。

rs.getLong("id"),

內容如下:

public List<Customer> findAll() {

return this.jdbcTemplate.query( "select id, name, address, weight from customer", new CustomerMapper());

}


private static final class CustomerMapper implements RowMapper<Customer> {


public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {

return new Customer(

rs.getLong("id"),

rs.getString("name"),

rs.getString("address"),

rs.getInt("weight")

);

}

}


使用java8的lambda語法,第二個參數會呼叫lambda (類似javascript的arrow function),一樣會收到兩個參數:rs跟rowNum。但是,java裡並沒有獨立的function/method,所以,lambda的運作方式比較像是產生一個只有一個方法的匿名類別。

public List<Customer> findAll() {

return this.jdbcTemplate.query( "select id, name, address, weight from customer",

(rs, rowNum) -> new Customer(

rs.getLong("id"),

rs.getString("name"),

rs.getString("address"),

rs.getInt("weight")

)

);

}

另外,在Spring Framework有一些stereotype (如: @Controller),使用資料庫時,我們會將使用@Repository,這樣才能利用@Autowired來使用這個類別。另外,還要實作CustomerDAO interface。

@Repository

public class CustomerDAODB implements CustomerDAO {

** 注意,要將CustomerDAOImpl的@Repository拿掉,或者將其中一個註記為@Primary,否則spring會顯示

Field dao in com.example.demo.controller.CustomerController required a single bean, but 2 were found:


Action:


Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

在這個程式(CustomerDAODB.java)裡,我們會使用到JdbcTemplate,也是一樣利用@Autowired來使用。@Autowired會自動讀取application.properties裡,spring.datasource的相關設定,並產生相對的connection (跟PHP類似),只是都被JdbcTemplate把這些細節都處理好了。

@Autowired

JdbcTemplate jdbcTemplate;

src\main\java\com\example\demo\dao\CustomerDAODB.java

package com.example.demo.dao;


import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.RowMapper;

import org.springframework.stereotype.Repository;


import com.example.demo.entity.Customer;


@Repository

public class CustomerDAODB implements CustomerDAO {


@Autowired

JdbcTemplate jdbcTemplate;

/*

// RowMapper可以改寫為java8的lambda語法

public List<Customer> findAll() {

return this.jdbcTemplate.query( "select id, name, address, weight from customer",

(rs, rowNum) ->

new Customer(

rs.getLong("id"),

rs.getString("name"),

rs.getString("address"),

rs.getInt("weight")

)

);

}

*/


public List<Customer> findAll() {

return this.jdbcTemplate.query( "select id, name, address, weight from customer", new CustomerMapper());

}


private static final class CustomerMapper implements RowMapper<Customer> {


public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {

return new Customer(

rs.getLong("id"),

rs.getString("name"),

rs.getString("address"),

rs.getInt("weight")

);

}

}


}

CustomerController.java

package com.example.demo.controller;


import java.sql.SQLException;

import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.GetMapping;


import com.example.demo.dao.CustomerDAO;

import com.example.demo.entity.Customer;


@RestController

public class CustomerController {

@Autowired

CustomerDAO dao;


//舊的寫法

//@RequestMapping(value = {"/customer"}, method = RequestMethod.GET)

@GetMapping(value = "/customer")

public List<Customer> retrieveCustomers() throws SQLException{

return dao.findAll();

}

}

新增、修改

CustomerDAO.java

package com.example.demo.dao;


import java.util.List;


import com.example.demo.entity.Customer;


public interface CustomerDAO {


public int insert(Customer customer);

public List<Customer> findAll();

public Customer findOne(Long id);

public int update(Customer customer);

public int delete(Long id);


}


大部分的內容前面說明過了,唯一需要補充的是queryForObject,queryForObject會接受一個陣列參數,依序對應到"?",回傳值就一樣利用RowMapper。

public Customer findOne(Long id) {

return this.jdbcTemplate.queryForObject(

"select id, name, address, weight from customer where id = ?",

new Object[]{id}, new CustomerMapper());

}

CustomerDAODB.java

package com.example.demo.dao;


import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.List;


import javax.sql.DataSource;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.RowMapper;

import org.springframework.stereotype.Repository;


import com.example.demo.entity.Customer;


@Repository

public class CustomerDAODB implements CustomerDAO {


@Autowired

private DataSource dataSource;

@Autowired

JdbcTemplate jdbcTemplate;


//jdbcTemplate

public Customer findOne(Long id) {

return this.jdbcTemplate.queryForObject(

"select id, name, address, weight from customer where id = ?",

new Object[]{id}, new CustomerMapper());

}


public List<Customer> findAll() {

return this.jdbcTemplate.query( "select id, name, address, weight from customer",

new CustomerMapper());

}


private static final class CustomerMapper implements RowMapper<Customer> {


public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {

Customer customer = new Customer();

customer.setId(rs.getLong("id"));

customer.setName(rs.getString("name"));

customer.setAddress(rs.getString("address"));

customer.setWeight(rs.getInt("weight"));

return customer;

}

}


public int insert(Customer customer) {

return jdbcTemplate.update(

"insert into customer (name, address, weight) values(?, ?, ?)",

customer.getName(), customer.getAddress(), customer.getWeight());

}

public int update(Customer customer) {

return jdbcTemplate.update(

"update customer set name=?, address=?, weight=? where id =?",

customer.getName(), customer.getAddress(), customer.getWeight(), customer.getId());

}


public int delete(Long id) {

return jdbcTemplate.update(

"delete from customer where id =?", id);

}


}

retrieveOneCustomer用到一個還沒說明的用法,就是REST通常會傳遞值

http://localhost:8080/customer/2

在這邊就利用{}來定義變數,再利用@PathVariable,將收到的變數轉換成參數。

@GetMapping(value = {"/customer/{id}"})

public Customer retrieveOneCustomer(@PathVariable("id") Long id) throws SQLException{

return dao.findOne(id);

}

另外,post跟put會收到傳來的內容,這邊就利用@RequestBody,將收到的變數轉換成參數。

@PostMapping(value = "/customer")

public void processFormCreate(@RequestBody Customer customer) throws SQLException {

dao.insert(customer);

}


@PutMapping(value = "/customer")

public void processFormUpdate(@RequestBody Customer customer) throws SQLException {

dao.update(customer);

}

CustomerController.java

package com.example.demo.controller;


import java.sql.SQLException;

import java.util.List;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.PutMapping;

import org.springframework.web.bind.annotation.DeleteMapping;

import org.springframework.web.bind.annotation.RequestBody;


import com.example.demo.dao.CustomerDAO;

import com.example.demo.entity.Customer;


@RestController

public class CustomerController {

@Autowired

CustomerDAO dao;

@GetMapping(value = {"/customer"})

public List<Customer> retrieveCustomers() throws SQLException{

return dao.findAll();

}


@GetMapping(value = {"/customer/{id}"})

public Customer retrieveOneCustomer(@PathVariable("id") Long id) throws SQLException{

return dao.findOne(id);

}


@PostMapping(value = "/customer")

public void processFormCreate(@RequestBody Customer customer) throws SQLException {

dao.insert(customer);

}


@PutMapping(value = "/customer")

public void processFormUpdate(@RequestBody Customer customer) throws SQLException {

dao.update(customer);

}


@DeleteMapping(value = "/customer/{id}")

public void deleteCustomer(@PathVariable("id") Long id) {

dao.delete(id);

}


}

利用

http://localhost:8080/customer/2

取得第二筆資料

{"id":2,"name":"Rich","address":"Taipei","weight":60}

試著利用postman送出POST跟PUT

GET

url:

http://localhost:8080/customer/1

POST

url:

http://localhost:8080/customer

header:

Content-Type:application/json

body: (raw / JSON (appication/json))

{

"name": "Rain",

"address": "Taipei",

"weight": 65

}

PUT

url:

http://localhost:8080/customer

header:

Content-Type:application/json

body: (raw / JSON (appication/json))

{

"id": 3,

"name": "Rain",

"address": "Tainan",

"weight": 67

}

DELETE

url:

http://localhost:8080/customer/3