javascript
Spring Boot - 构建数据访问层
文章目錄
- 基礎規范: JDBC 關系型數據庫訪問規范
- JDBC 規范中的核心編程對象
- DriverManager
- DataSource
- Connection
- Statement/PreparedStatement
- ResultSet
- 如何使用 JDBC 規范訪問數據庫
- 數據訪問: JdbcTemplate 訪問關系型數據庫
- 數據模型和 Repository 層設計
- Domain設計
- 數據模型
- 抽象數據庫訪問的入口
- 原生的實現
- 使用 JdbcTemplate 操作數據庫
- 使用 JdbcTemplate 實現查詢
- 使用 JdbcTemplate 實現插入
- 使用 SimpleJdbcInsert 簡化數據插入過程
基礎規范: JDBC 關系型數據庫訪問規范
我們將進入 Spring Boot 另一個核心技術體系的討論,即數據訪問技術體系。無論是互聯網應用還是傳統軟件,對于任何一個系統而言,數據的存儲和訪問都是不可缺少的。
數據訪問層的構建可能會涉及多種不同形式的數據存儲媒介,這里關注的是最基礎也是最常用的數據存儲媒介,即關系型數據庫,針對關系型數據庫,Java 中應用最廣泛的就是 JDBC 規范,今天我們將對這個經典規范展開討論。
JDBC 是 Java Database Connectivity 的全稱,它的設計初衷是提供一套能夠應用于各種數據庫的統一標準,這套標準需要不同數據庫廠家之間共同遵守,并提供各自的實現方案供 JDBC 應用程序調用。
作為一套統一標準,JDBC 規范具備完整的架構體系,如下圖所示:
可以看到,Java 應用程序通過 JDBC 所提供的 API 進行數據訪問,而這些 API 中包含了開發人員所需要掌握的各個核心編程對象
JDBC 規范中的核心編程對象
對于日常開發而言,JDBC 規范中的核心編程對象包括 DriverManger、DataSource、Connection、Statement,及 ResultSet。
DriverManager
JDBC 中的 DriverManager 主要負責加載各種不同的驅動程序(Driver),并根據不同的請求向應用程序返回相應的數據庫連接(Connection),應用程序再通過調用 JDBC API 實現對數據庫的操作。
JDBC 中的 Driver 定義如下,其中最重要的是第一個獲取 Connection 的 connect 方法:
public interface Driver {//獲取數據庫連接Connection connect(String url, java.util.Properties info)throws SQLException;boolean acceptsURL(String url) throws SQLException;DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)throws SQLException;int getMajorVersion();int getMinorVersion();boolean jdbcCompliant();public Logger getParentLogger() throws SQLFeatureNotSupportedException; }針對 Driver 接口,不同的數據庫供應商分別提供了自身的實現方案。例如,MySQL 中的 Driver 實現類如下代碼所示:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {// 通過 DriverManager 注冊 Driverstatic {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException("Can't register driver!");}}…}這里就使用用了 DriverManager,而 DriverManager 除提供了上述用于注冊 Driver 的 registerDriver 方法之外,還提供了 getConnection 方法用于針對具體的 Driver 獲取 Connection 對象。
DataSource
我們知道在 JDBC 規范中可直接通過 DriverManager 獲取 Connection,我們也知道獲取 Connection 的過程需要建立與數據庫之間的連接,而這個過程會產生較大的系統開銷。
為了提高性能,通常我們首先會建立一個中間層將 DriverManager 生成的 Connection 存放到連接池中,再從池中獲取 Connection。
而我們可以認為 DataSource 就是這樣一個中間層,它作為 DriverManager 的替代品而推出,是獲取數據庫連接的首選方法。
DataSource 在 JDBC 規范中代表的是一種數據源,核心作用是獲取數據庫連接對象 Connection。在日常開發過程中,我們通常會基于 DataSource 獲取 Connection。DataSource 接口的定義如下代碼所示:
public interface DataSource extends CommonDataSource, Wrapper {Connection getConnection() throws SQLException;Connection getConnection(String username, String password) throws SQLException;}從上面我們可以看到,DataSource 接口提供了兩個獲取 Connection 的重載方法,并繼承了 CommonDataSource 接口。CommonDataSource 是 JDBC 中關于數據源定義的根接口,除了 DataSource 接口之外,它還有另外兩個子接口,如下圖所示:
其中,DataSource 是官方定義的獲取 Connection 的基礎接口,XADataSource 用來在分布式事務環境下實現 Connection 的獲取,而 ConnectionPoolDataSource 是從連接池 ConnectionPool 中獲取 Connection 的接口。
所謂的 ConnectionPool 相當于預先生成一批 Connection 并存放在池中,從而提升 Connection 獲取的效率。
請注意 DataSource 接口同時還繼承了一個 Wrapper 接口。從接口的命名上看,我們可以判斷該接口起到一種包裝器的作用。事實上,因為很多數據庫供應商提供了超越標準 JDBC API 的擴展功能,所以 Wrapper 接口可以把一個由第三方供應商提供的、非 JDBC 標準的接口包裝成標準接口。
以 DataSource 接口為例,如果我們想自己實現一個定制化的數據源類 MyDataSource,就可以提供一個實現了 Wrapper 接口的 MyDataSourceWrapper 類來完成包裝和適配,如下圖所示:
在 JDBC 規范中,除了 DataSource 之外,Connection、Statement、ResultSet 等核心對象也都繼承了這個 Wrapper 接口。
作為一種基礎組件,它同樣不需要開發人員自己實現 DataSource,因為業界已經存在了很多優秀的實現方案,如 DBCP、C3P0 和 Druid 等。
例如 Druid 提供了 DruidDataSource,它不僅提供了連接池的功能,還提供了諸如監控等其他功能,它的類層結構如下圖所示:
Connection
DataSource 的目的是獲取 Connection 對象。我們可以把 Connection 理解為一種會話(Session)機制,Connection 代表一個數據庫連接,負責完成與數據庫之間的通信。
所有 SQL 的執行都是在某個特定 Connection 環境中進行的,同時它還提供了一組重載方法分別用于創建 Statement 和 PreparedStatement。另一方面,Connection 也涉及事務相關的操作。
Connection 接口中定義的方法很豐富,其中最核心的幾個方法如下代碼所示:
public interface Connection extends Wrapper, AutoCloseable {//創建 StatementStatement createStatement() throws SQLException;//創建 PreparedStatementPreparedStatement prepareStatement(String sql) throws SQLException;//提交void commit() throws SQLException;//回滾void rollback() throws SQLException;//關閉連接void close() throws SQLException;}這里涉及具體負責執行 SQL 語句的 Statement 和 PreparedStatement 對象,我們接著往下看。
Statement/PreparedStatement
JDBC 規范中的 Statement 存在兩種類型,一種是普通的 Statement,一種是支持預編譯的 PreparedStatement。
所謂預編譯,是指數據庫的編譯器會對 SQL 語句提前編譯,然后將預編譯的結果緩存到數據庫中,下次執行時就可以通過替換參數并直接使用編譯過的語句,從而大大提高 SQL 的執行效率。
當然,這種預編譯也需要一定成本,因此在日常開發中,如果對數據庫只執行一次性讀寫操作時,用 Statement 對象進行處理會比較合適;而涉及 SQL 語句的多次執行時,我們可以使用 PreparedStatement。
如果需要查詢數據庫中的數據,我們只需要調用 Statement 或 PreparedStatement 對象的 executeQuery 方法即可。
這個方法以 SQL 語句作為參數,執行完后返回一個 JDBC 的 ResultSet 對象。當然,Statement 或 PreparedStatement 還提供了一大批執行 SQL 更新和查詢的重載方法,我們無意一一展開。
以 Statement 為例,它的核心方法如下代碼所示:
public interface Statement extends Wrapper, AutoCloseable {//執行查詢語句ResultSet executeQuery(String sql) throws SQLException; //執行更新語句int executeUpdate(String sql) throws SQLException; //執行 SQL 語句boolean execute(String sql) throws SQLException; //執行批處理int[] executeBatch() throws SQLException;}這里我們同樣引出了 JDBC 規范中最后一個核心編程對象,即代表執行結果的 ResultSet。
ResultSet
一旦我們通過 Statement 或 PreparedStatement 執行了 SQL 語句并獲得了 ResultSet 對象,就可以使用該對象中定義的一大批用于獲取 SQL 執行結果值的工具方法,如下代碼所示:
public interface ResultSet extends Wrapper, AutoCloseable {//獲取下一個結果boolean next() throws SQLException;//獲取某一個類型的結果值Value getXXX(int columnIndex) throws SQLException;…}如何使用 JDBC 規范訪問數據庫
對于開發人員而言,JDBC API 是我們訪問數據庫的主要途徑,如果我們使用 JDBC 開發一個訪問數據庫的執行流程,常見的代碼風格如下所示(省略了異常處理):
// 創建池化的數據源PooledDataSource dataSource = new PooledDataSource ();// 設置 MySQL DriverdataSource.setDriver ("com.mysql.jdbc.Driver");// 設置數據庫 URL、用戶名和密碼dataSource.setUrl ("jdbc:mysql://localhost:3306/test");dataSource.setUsername("root");dataSource.setPassword("root");// 獲取連接Connection connection = dataSource.getConnection();// 執行查詢PreparedStatement statement = connection.prepareStatement ("select * from user");// 獲取查詢結果進行處理ResultSet resultSet = statement.executeQuery();while (resultSet.next()) {…}// 關閉資源statement.close();resultSet.close();connection.close();這段代碼中完成了對基于前面介紹的 JDBC API 中的各個核心編程對象的數據訪問。上述代碼主要面向查詢場景,而針對用于插入數據的處理場景,我們只需要在上述代碼中替換幾行代碼,即將“執行查詢”和“獲取查詢結果進行處理”部分的查詢操作代碼替換為插入操作代碼就行。
最后,我們梳理一下基于 JDBC 規范進行數據庫訪問的整個開發流程,如下圖所示:
我們明確地將基于 JDBC 規范訪問關系型數據庫的操作分成兩大部分:一部分是準備和釋放資源以及執行 SQL 語句,另一部分則是處理 SQL 執行結果。
而對于任何數據訪問而言,前者實際上都是重復的。在上圖所示的整個開發流程中,事實上只有“處理 ResultSet ”部分的代碼需要開發人員根據具體的業務對象進行定制化處理。這種抽象為整個執行過程提供了優化空間。諸如 Spring 框架中 JdbcTemplate 這樣的模板工具類就應運而生了
數據訪問: JdbcTemplate 訪問關系型數據庫
JDBC 規范是 Java 領域中使用最廣泛的數據訪問標準,目前市面上主流的數據訪問框架都是構建在 JDBC 規范之上。
因為 JDBC 是偏底層的操作規范,所以關于如何使用 JDBC 規范進行關系型數據訪問的實現方式有很多(區別在于對 JDBC 規范的封裝程度不同),而在 Spring 中,同樣提供了 JdbcTemplate 模板工具類實現數據訪問,它簡化了 JDBC 規范的使用方法,jiex我們將圍繞這個模板類展開討論。
數據模型和 Repository 層設計
我們知道一個訂單中往往涉及一個或多個商品, 我們主要通過一對多的關系來展示數據庫設計和實現方面的技巧。而為了使描述更簡單,我們把具體的業務字段做了簡化。Order 類的定義如下代碼所示:
Domain設計
public class Order{private Long id; //訂單Idprivate String orderNumber; //訂單編號private String deliveryAddress; //物流地址private List<Goods> goodsList; //商品列表//省略了 getter/setter}其中代表商品的 Goods 類定義如下:
public class Goods {private Long id; //商品Idprivate String goodsCode; //商品編號private String goodsName; //商品名稱private Double price; //商品價格//省略了 getter/setter}數據模型
從以上代碼,我們不難看出一個訂單可以包含多個商品,因此設計關系型數據庫表時,我們首先會構建一個中間表來保存 Order 和 Goods 這層一對多關系。
DROP TABLE IF EXISTS `order`;DROP TABLE IF EXISTS `goods`;DROP TABLE IF EXISTS `order_goods`;create table `order` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`order_number` varchar(50) not null,`delivery_address` varchar(100) not null,`create_time` timestamp not null DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`));create table `goods` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`goods_code` varchar(50) not null,`goods_name` varchar(50) not null,`goods_price` double not null,`create_time` timestamp not null DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`));create table `order_goods` (`order_id` bigint(20) not null,`goods_id` bigint(20) not null,foreign key(`order_id`) references `order`(`id`),foreign key(`goods_id`) references `goods`(`id`));抽象數據庫訪問的入口
基于以上數據模型,我們將完成 order-server 中的 Repository 層組件的設計和實現。首先,我們需要設計一個 OrderRepository 接口,用來抽象數據庫訪問的入口,如下代碼所示:
public interface OrderRepository {Order addOrder(Order order);Order getOrderById(Long orderId);Order getOrderDetailByOrderNumber(String orderNumber);}這個接口非常簡單,方法都是自解釋的。不過請注意,這里的 OrderRepository 并沒有繼承任何父接口,完全是一個自定義的、獨立的 Repository。
針對上述 OrderRepository 中的接口定義,我們將構建一系列的實現類。
-
OrderRawJdbcRepository:使用原生 JDBC 進行數據庫訪問
-
OrderJdbcRepository:使用 JdbcTemplate 進行數據庫訪問
-
OrderJpaRepository:使用 Spring Data JPA 進行數據庫訪問
原生的實現
OrderRawJdbcRepository 類中實現方法如下代碼所示:
@Repository("orderRawJdbcRepository") public class OrderRawJdbcRepository implements OrderRepository {@Autowiredprivate DataSource dataSource;@Overridepublic Order getOrderById(Long orderId) {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {connection = dataSource.getConnection();statement = connection.prepareStatement("select id, order_number, delivery_address from `order` where id=?");statement.setLong(1, orderId);resultSet = statement.executeQuery();Order order = null;if (resultSet.next()) {order = new Order(resultSet.getLong("id"), resultSet.getString("order_number"),resultSet.getString("delivery_address"));}return order;} catch (SQLException e) {System.out.print(e);} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {}}if (statement != null) {try {statement.close();} catch (SQLException e) {}}if (connection != null) {try {connection.close();} catch (SQLException e) {}}}return null;}//省略其他 OrderRepository 接口方法實現}這里,值得注意的是,我們首先需要在類定義上添加 @Repository 注解,標明這是能夠被 Spring 容器自動掃描的 Javabean,再在 @Repository 注解中指定這個 Javabean 的名稱為"orderRawJdbcRepository",方便 Service 層中根據該名稱注入 OrderRawJdbcRepository 類。
可以看到,上述代碼使用了 JDBC 原生 DataSource、Connection、PreparedStatement、ResultSet 等核心編程對象完成針對“order”表的一次查詢。代碼流程看起來比較簡單,其實也比較煩瑣,學到這里,我們可以結合上一課時的內容理解上述代碼。
請注意,如果我們想運行這些代碼,千萬別忘了在 Spring Boot 的配置文件中添加對 DataSource 的定義,如下代碼所示:
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/appointmentusername: rootpassword: root使用 JdbcTemplate 操作數據庫
要想在應用程序中使用 JdbcTemplate,首先我們需要引入對它的依賴,如下代碼所示:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency>JdbcTemplate 提供了一系列的 query、update、execute 重載方法應對數據的 CRUD 操作。
使用 JdbcTemplate 實現查詢
我們先來討論一下最簡單的查詢操作,并對 OrderRawJdbcRepository 中的 getOrderById 方法進行重構。為此,我們構建了一個新的 OrderJdbcRepository 類并同樣實現了 OrderRepository 接口,如下代碼所示:
@Repository("orderJdbcRepository")public class OrderJdbcRepository implements OrderRepository {private JdbcTemplate jdbcTemplate;@Autowiredpublic OrderJdbcRepository(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}}可以看到,這里通過構造函數注入了 JdbcTemplate 模板類。
而 OrderJdbcRepository 的 getOrderById 方法實現過程如下代碼所示:
@Overridepublic Order getOrderById(Long orderId) {Order order = jdbcTemplate.queryForObject("select id, order_number, delivery_address from `order` where id=?",this::mapRowToOrder, orderId);return order;}顯然,這里使用了 JdbcTemplate 的 queryForObject 方法執行查詢操作,該方法傳入目標 SQL、參數以及一個 RowMapper 對象。其中 RowMapper 定義如下:
public interface RowMapper<T> {T mapRow(ResultSet rs, int rowNum) throws SQLException;}從 mapRow 方法定義中,我們不難看出 RowMapper 的作用就是處理來自 ResultSet 中的每一行數據,并將來自數據庫中的數據映射成領域對象。例如,使用 getOrderById 中用到的 mapRowToOrder 方法完成對 Order 對象的映射,如下代碼所示:
private Order mapRowToOrder(ResultSet rs, int rowNum) throws SQLException {return new Order(rs.getLong("id"), rs.getString("order_number"), rs.getString("delivery_address"));}講到這里,你可能注意到 getOrderById 方法實際上只是獲取了 Order 對象中的訂單部分信息,并不包含商品數據。
接下來,我們再來設計一個 getOrderDetailByOrderNumber 方法,根據訂單編號獲取訂單以及訂單中所包含的所有商品信息,如下代碼所示:
@Overridepublic Order getOrderDetailByOrderNumber(String orderNumber) {//獲取 Order 基礎信息Order order = jdbcTemplate.queryForObject("select id, order_number, delivery_address from `order` where order_number=?", this::mapRowToOrder,orderNumber);if (order == null)return order;//獲取 Order 與 Goods 之間的關聯關系,找到給 Order 中的所有 GoodsIdLong orderId = order.getId();List<Long> goodsIds = jdbcTemplate.query("select order_id, goods_id from order_goods where order_id=?",new ResultSetExtractor<List<Long>>() {public List<Long> extractData(ResultSet rs) throws SQLException, DataAccessException {List<Long> list = new ArrayList<Long>();while (rs.next()) {list.add(rs.getLong("goods_id"));}return list;}}, orderId);//根據 GoodsId 分別獲取 Goods 信息并填充到 Order 對象中for (Long goodsId : goodsIds) {Goods goods = getGoodsById(goodsId);order.addGoods(goods);}return order;}首先,我們獲取 Order 基礎信息,并通過 Order 中的 Id 編號從中間表中獲取所有 Goods 的 Id 列表,通過遍歷這個 Id 列表再分別獲取 Goods 信息,最后將 Goods 信息填充到 Order 中,從而構建一個完整的 Order 對象。
這里通過 Id 獲取 Goods 數據的實現方法也與 getOrderById 方法的實現過程一樣,如下代碼所示
private Goods getGoodsById(Long goodsId) {return jdbcTemplate.queryForObject("select id, goods_code, goods_name, price from goods where id=?",this::mapRowToGoods, goodsId);}private Goods mapRowToGoods(ResultSet rs, int rowNum) throws SQLException {return new Goods(rs.getLong("id"), rs.getString("goods_code"), rs.getString("goods_name"),rs.getDouble("price"));}使用 JdbcTemplate 實現插入
在 JdbcTemplate 中,我們可以通過 update 方法實現數據的插入和更新。針對 Order 和 Goods 中的關聯關系,插入一個 Order 對象需要同時完成兩張表的更新,即 order 表和 order_goods 表,因此插入 Order 的實現過程也分成兩個階段,如下代碼所示的 addOrderWithJdbcTemplate 方法展示了這一過程:
private Order addOrderDetailWithJdbcTemplate(Order order) {//插入 Order 基礎信息Long orderId = saveOrderWithJdbcTemplate(order);order.setId(orderId);//插入 Order 與 Goods 的對應關系List<Goods> goodsList = order.getGoods();for (Goods goods : goodsList) {saveGoodsToOrderWithJdbcTemplate(goods, orderId);}return order;}可以看到,這里同樣先是插入 Order 的基礎信息,然后再遍歷 Order 中的 Goods 列表并逐條進行插入。其中的 saveOrderWithJdbcTemplate 方法如下代碼所示:
private Long saveOrderWithJdbcTemplate(Order order) {PreparedStatementCreator psc = new PreparedStatementCreator() {@Overridepublic PreparedStatement createPreparedStatement(Connection con) throws SQLException {PreparedStatement ps = con.prepareStatement("insert into `order` (order_number, delivery_address) values (?, ?)",Statement.RETURN_GENERATED_KEYS);ps.setString(1, order.getOrderNumber());ps.setString(2, order.getDeliveryAddress());return ps;}};KeyHolder keyHolder = new GeneratedKeyHolder();jdbcTemplate.update(psc, keyHolder);return keyHolder.getKey().longValue();}上述 saveOrderWithJdbcTemplate 的方法比想象中要復雜,主要原因在于我們需要在插入 order 表的同時返回數據庫中所生成的自增主鍵,因此,這里使用了 PreparedStatementCreator 工具類封裝 PreparedStatement 對象的構建過程,并在 PreparedStatement 的創建過程中設置了 Statement.RETURN_GENERATED_KEYS 用于返回自增主鍵。然后我們構建了一個 GeneratedKeyHolder 對象用于保存所返回的自增主鍵。這是使用 JdbcTemplate 實現帶有自增主鍵數據插入的一種標準做法,你可以參考這一做法并應用到日常開發過程中。
至于用于插入 Order 與 Goods 關聯關系的 saveGoodsToOrderWithJdbcTemplate 方法就比較簡單了,直接調用 JdbcTemplate 的 update 方法插入數據即可,如下代碼所示:
private void saveGoodsToOrderWithJdbcTemplate(Goods goods, long orderId) {jdbcTemplate.update("insert into order_goods (order_id, goods_id) " + "values (?, ?)", orderId, goods.getId());}接下來,我們需要實現插入 Order 的整個流程,先實現 Service 類和 Controller 類,如下代碼所示:
@Servicepublic class OrderService {@Autowired@Qualifier("orderJdbcRepository")private OrderRepository orderRepository;public Order addOrder(Order order) {return orderRepository.addOrder(order);} }@RestController@RequestMapping(value="orders")public class OrderController {@RequestMapping(value = "", method = RequestMethod.POST)public Order addOrder(@RequestBody Order order) {Order result = orderService.addOrder(order);return result;}}這兩個類都是直接對 orderJdbcRepository 中的方法進行封裝調用,操作非常簡單。然后,我們打開 Postman,并在請求消息體中輸入如下內容:
{"orderNumber" : "Order10002","deliveryAddress" : "test_address2","goods": [{"id": 1,"goodsCode": "GoodsCode1","goodsName": "GoodsName1","price": 100.0}]}通過 Postman 向http://localhost:8081/orders端點發起 Post 請求后,我們發現 order 表和 order_goods 表中的數據都已經正常插入。
使用 SimpleJdbcInsert 簡化數據插入過程
雖然通過 JdbcTemplate 的 update 方法可以完成數據的正確插入,我們不禁發現這個實現過程還是比較復雜,尤其是涉及自增主鍵的處理時,代碼顯得有點臃腫。那么有沒有更加簡單的實現方法呢?
答案是肯定的,Spring Boot 針對數據插入場景專門提供了一個 SimpleJdbcInsert 工具類,SimpleJdbcInsert 本質上是在 JdbcTemplate 的基礎上添加了一層封裝,提供了一組 execute、executeAndReturnKey 以及 executeBatch 重載方法來簡化數據插入操作。
通常,我們可以在 Repository 實現類的構造函數中對 SimpleJdbcInsert 進行初始化,如下代碼所示:
private JdbcTemplate jdbcTemplate;private SimpleJdbcInsert orderInserter;private SimpleJdbcInsert orderGoodsInserter;public OrderJdbcRepository(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;this.orderInserter = new SimpleJdbcInsert(jdbcTemplate).withTableName("`order`").usingGeneratedKeyColumns("id");this.orderGoodsInserter = new SimpleJdbcInsert(jdbcTemplate).withTableName("order_goods");}可以看到,這里首先注入了一個 JdbcTemplate 對象,然后我們基于 JdbcTemplate 并針對 order 表和 order_goods 表分別初始化了兩個 SimpleJdbcInsert 對象 orderInserter 和 orderGoodsInserter。其中 orderInserter 中還使用了 usingGeneratedKeyColumns 方法設置自增主鍵列。
基于 SimpleJdbcInsert,完成 Order 對象的插入就非常簡單了,實現方式如下所示
private Long saveOrderWithSimpleJdbcInsert(Order order) {Map<String, Object> values = new HashMap<String, Object>();values.put("order_number", order.getOrderNumber());values.put("delivery_address", order.getDeliveryAddress());Long orderId = orderInserter.executeAndReturnKey(values).longValue();return orderId;}我們通過構建一個 Map 對象,然后把需要添加的字段設置成一個個鍵值對。通過SimpleJdbcInsert 的 executeAndReturnKey 方法在插入數據的同時直接返回自增主鍵。同樣,完成 order_goods 表的操作只需要幾行代碼就可以了,如下代碼所示:
private void saveGoodsToOrderWithSimpleJdbcInsert(Goods goods, long orderId) {Map<String, Object> values = new HashMap<>();values.put("order_id", orderId);values.put("goods_id", goods.getId());orderGoodsInserter.execute(values);}這里用到了 SimpleJdbcInsert 提供的 execute 方法,我們可以把這些方法組合起來對 addOrderDetailWithJdbcTemplate 方法進行重構,從而得到如下所示的 addOrderDetailWithSimpleJdbcInsert 方法:
private Order addOrderDetailWithSimpleJdbcInsert(Order order) {//插入 Order 基礎信息Long orderId = saveOrderWithSimpleJdbcInsert(order);order.setId(orderId);//插入 Order 與 Goods 的對應關系List<Goods> goodsList = order.getGoods();for (Goods goods : goodsList) {saveGoodsToOrderWithSimpleJdbcInsert(goods, orderId);}return order;}總結
以上是生活随笔為你收集整理的Spring Boot - 构建数据访问层的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot - 自动配置实现
- 下一篇: Spring - BeanDefinit