你真的了解ORM嗎?通過一個簡單的例子來學習ORM

什么是ORM

ORM(Object-Relational Mapping)是一種將面向對象程序數據模型與關系數據庫之間進行映射的技術。

比如數據庫表user,它有id、name、age字段映射到Java實體類就是User類,有id、name、age屬性。

CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
@Entity
@Table(name = "user")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private int id;private String name;private int age;// 省略setter和getter
}

什么是JPA

JPA(Java Persistence API)(Java持久化接口)是Java平臺提供的一套標準化的持久化框架,用于簡化Java對象與數據庫之間的交互。

JPA幫你隱藏了底層數據庫細節,你只需要操作對象,而不需要編寫復雜的SQL語句,JPA會幫你自動生成相應的SQL語句并執行。

其實JPA就是幫我定義好了一系列注解,它不提供具體的實現,只是定規范。如下:

下面這些注解是否很熟悉

@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface Entity {String name() default "";
}public @interface OneToMany {Class targetEntity() default void.class;CascadeType[] cascade() default {};FetchType fetch() default LAZY;String mappedBy() default "";boolean orphanRemoval() default false;
}@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface Column {String name() default "";boolean unique() default false;boolean nullable() default true;boolean insertable() default true;boolean updatable() default true;String columnDefinition() default "";String table() default "";int length() default 255;int precision() default 0;int scale() default 0;
}

就是JPA只定義接口規范,具體怎么實現各個廠家自己去做,以下是基于JPA的常用的框架。

  • Hibernate
  • OpenJPA
  • Spring Data JPA

自己封裝一個ORM框架

下面通過一個代碼示例來自己封裝一個簡單的ORM框架。

引入依賴包,就是mysql-connector-java和javax.persistence-api

<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>javax.persistence</groupId><artifactId>javax.persistence-api</artifactId><version>2.2</version></dependency>
</dependencies>

如下是JDBC獲取數據庫連接

import java.sql.Connection;
import java.sql.DriverManager;public class ConnectionUtil {public static Connection getConnection() throws Exception {String url = "jdbc:mysql://localhost:3306/jdbc_orm?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false";String user = "root";String password = "123456";return DriverManager.getConnection(url, user, password);}
}

具體的ORM核心代碼,用Java的反射技術來實現。

import javax.persistence.Entity;
import javax.persistence.Id;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;public class OrmUtil {private final Connection connection;public OrmUtil(Connection connection) {this.connection = connection;}public <T> void save(T entity) throws Exception {// 獲取實體對象的Class對象Class<?> clazz = entity.getClass();// 獲取實體對象所對應的數據庫表名String tableName = getTableName(clazz);// 存儲實體對象的列名和對應的值List<String> columns = new ArrayList<>();List<Object> values = new ArrayList<>();// 獲取實體對象的字段Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 設置字段可訪問field.setAccessible(true);// 存儲字段名columns.add(field.getName());// 存儲字段值,由于是Object類型,需要強制類型轉換values.add(field.get(entity));}// 構建SQL語句StringBuilder sqlBuilder = new StringBuilder();sqlBuilder.append("INSERT INTO ").append(tableName).append(" (");for (int i = 0; i < columns.size(); i++) {sqlBuilder.append(columns.get(i));if (i < columns.size() - 1) {sqlBuilder.append(", ");}}sqlBuilder.append(") VALUES (");for (int i = 0; i < values.size(); i++) {sqlBuilder.append("?");if (i < values.size() - 1) {sqlBuilder.append(", ");}}sqlBuilder.append(")");String sql = sqlBuilder.toString();// 打印SQL語句System.out.println("執行新增語句;" + sql);// 執行SQL語句try (PreparedStatement statement = connection.prepareStatement(sql)) {for (int i = 0; i < values.size(); i++) {// 設置參數,由于參數類型不確定,使用setObject方法statement.setObject(i + 1, values.get(i));}statement.executeUpdate();}}public <T> void update(T entity) throws Exception {// 獲取實體對象的Class對象Class<?> clazz = entity.getClass();// 獲取實體對象所對應的數據庫表名String tableName = getTableName(clazz);// 存儲實體對象的非主鍵列名和對應的值List<String> columns = new ArrayList<>();List<Object> values = new ArrayList<>();// 存儲主鍵值Object idValue = null;// 獲取實體對象的字段Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 設置字段可訪問field.setAccessible(true);// 判斷字段是否有@Id注解,若有,則將其值作為主鍵值存儲if (field.isAnnotationPresent(Id.class)) {idValue = field.get(entity);} else {// 若沒有@Id注解,則將其列名和值存儲columns.add(field.getName());values.add(field.get(entity));}}// 構建SQL語句StringBuilder sqlBuilder = new StringBuilder();sqlBuilder.append("UPDATE ").append(tableName).append(" SET ");for (int i = 0; i < columns.size(); i++) {sqlBuilder.append(columns.get(i)).append(" = ?");if (i < columns.size() - 1) {sqlBuilder.append(", ");}}sqlBuilder.append(" WHERE id = ?");String sql = sqlBuilder.toString();System.out.println("執行更新語句;" + sql);// 執行SQL語句try (PreparedStatement statement = connection.prepareStatement(sql)) {for (int i = 0; i < values.size(); i++) {// 設置非主鍵列的參數值statement.setObject(i + 1, values.get(i));}// 設置主鍵參數值statement.setObject(values.size() + 1, idValue);statement.executeUpdate();}}public void delete(Class<?> clazz, Object id) throws Exception {// 獲取實體對象所對應的數據庫表名String tableName = getTableName(clazz);// 構建SQL語句String sql = "DELETE FROM " + tableName + " WHERE id = ?";System.out.println("執行刪除語句:" + sql);// 執行SQL語句try (PreparedStatement statement = connection.prepareStatement(sql)) {// 設置參數值statement.setObject(1, id);// 執行刪除操作statement.executeUpdate();}}public <T> T findById(Class<T> clazz, Object id) throws Exception {// 獲取實體對象所對應的數據庫表名String tableName = getTableName(clazz);// 構建SQL語句String sql = "SELECT * FROM " + tableName + " WHERE id = ?";System.out.println("執行查詢語句;" + sql);// 執行SQL查詢try (PreparedStatement statement = connection.prepareStatement(sql)) {// 設置參數值statement.setObject(1, id);// 執行查詢操作并獲取結果集try (ResultSet resultSet = statement.executeQuery()) {// 如果有結果,則創建實體對象并設置字段值if (resultSet.next()) {T entity = clazz.newInstance();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {field.setAccessible(true);// 根據字段名從結果集中獲取對應的值,并設置到實體對象中Object value = resultSet.getObject(field.getName());field.set(entity, value);}return entity;}}}return null;}private String getTableName(Class<?> clazz) {// 判斷類是否有@Entity注解if (clazz.isAnnotationPresent(Entity.class)) {// 獲取類上的@Entity注解對象Entity entityAnnotation = clazz.getAnnotation(Entity.class);// 獲取注解中的表名String tableName = entityAnnotation.name();// 如果注解中的表名為空,則將類名轉換為小寫作為表名if (tableName.isEmpty()) {tableName = clazz.getSimpleName().toLowerCase();}return tableName;}// 如果類沒有@Entity注解,拋出異常throw new IllegalArgumentException("類沒有@Entity注解");}
}

這段代碼是一個簡單的ORM(對象關系映射)工具類,可以通過調用其提供的save、update、delete、findById等方法,實現對數據庫表的增、刪、改、查操作。

在這段代碼中主要包括以下內容:

  1. 通過反射獲取類的屬性、方法和注解等信息;

  2. 通過PreparedStatement實現對數據庫的增、刪、改、查等操作;

  3. 對Java泛型機制的運用,如在save、update、findById等方法中,將類名和主鍵值等作為方法參數,以達到通用的效果;

  4. 對JPA注解的運用,如對@Id、@Entity等注解的解析、獲取注解值等操作。


測試增刪改查

import java.sql.Connection;public class Test {public static void main(String[] args) {try {// 獲取數據庫連接Connection connection = ConnectionUtil.getConnection();// 創建一個實體管理器OrmUtil ormUtil = new OrmUtil(connection);// 創建一個User對象User user = new User();user.setId(1);user.setName("張三");user.setAge(28);// 保存User對象到數據庫ormUtil.save(user);// 修改User對象user.setAge(30);ormUtil.update(user);// 根據id查詢User對象User foundUser = ormUtil.findById(User.class, 1);System.out.println("查詢結果:"+foundUser); // 輸出:"張三"// 刪除User對象ormUtil.delete(User.class, 1);} catch (Exception e) {e.printStackTrace();}}
}

結語

以上只是最簡單的增刪改查,實際的ORM框架如Hibernate的原理都是差不多的,只是提供的增刪改查的接口更多更豐富。還是要對Java的反射運用要深入。

關注微信公眾號:“小虎哥的技術博客”。我們會定期發布關于Java技術的詳盡文章,讓您能夠深入了解該領域的各種技巧和方法,讓我們一起成為更優秀的程序員👩?💻👨?💻!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/35846.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/35846.shtml
英文地址,請注明出處:http://en.pswp.cn/news/35846.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

2023國賽 高教社杯數學建模ABCDE題思路匯總分析

文章目錄 0 賽題思路1 競賽信息2 競賽時間3 建模常見問題類型3.1 分類問題3.2 優化問題3.3 預測問題3.4 評價問題 4 建模資料 0 賽題思路 &#xff08;賽題出來以后第一時間在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 競賽信息 全國大學生數學建模…

echarts加釣魚島赤尾嶼(vue)(親測有效)

1.首先引入json文件&#xff0c;node_modules/echarts中就有 import chinaData from "../../node_modules/echarts/map/json/china.json" 2.初始化地圖&#xff0c;在初始化地圖的時候加入釣魚島和赤尾嶼的數據&#xff0c;在chinaData下的features中加入即可&#x…

Design-Pattern設計模式

Design-Pattern設計模式 圖說設計模式 圖說設計模式 在線書籍 軟件模式是將模式的一般概念應用于軟件開發領域&#xff0c;即軟件開發的 總體指導思路或參照樣板。軟件模式并非僅限于設計模式&#xff0c;還包括 架構模式、分析模式和過程模式等&#xff0c;實際上&#xff…

FFmpeg常見命令行(四):FFmpeg流媒體

前言 在Android音視頻開發中&#xff0c;網上知識點過于零碎&#xff0c;自學起來難度非常大&#xff0c;不過音視頻大牛Jhuster提出了《Android 音視頻從入門到提高 - 任務列表》&#xff0c;結合我自己的工作學習經歷&#xff0c;我準備寫一個音視頻系列blog。本文是音視頻系…

leetcode做題筆記77組合

給定兩個整數 n 和 k&#xff0c;返回范圍 [1, n] 中所有可能的 k 個數的組合。 你可以按 任何順序 返回答案。 思路一&#xff1a;直接求出組合數將每個組合放進數組中 int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {int size 0, num 1, i;in…

Rust中的智能指針:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak<T>

Rust中的智能指針是什么 智能指針&#xff08;smart pointers&#xff09;是一類數據結構&#xff0c;是擁有數據所有權和額外功能的指針。是指針的進一步發展 指針&#xff08;pointer&#xff09;是一個包含內存地址的變量的通用概念。這個地址引用&#xff0c;或 ” 指向”…

UML 類圖的畫法

1.類圖的畫法 類 整體是個矩形&#xff0c;第一層類名&#xff0c;第二層屬性&#xff0c;第三層方法。 &#xff1a;public- : private# : protected空格: 默認的default 對應的類寫法。 public class Student {public String name;public Integer age;protected I…

2023杭電第七場補題報告1002 1004 1011 1013

2023杭電第七場補題報告1002 1004 1011 1013 1002 B. Random Nim Game (hdu.edu.cn) 思路 手推一下就可以發現其實除了一次必定結束的其他情況概論都是 1 2 \frac{1}{2} 21? 代碼 #include <bits/stdc.h> using namespace std; #define int long long void solve()…

【hello C++】特殊類設計

目錄 一、設計一個類&#xff0c;不能被拷貝 二、設計一個類&#xff0c;只能在堆上創建對象 三、設計一個類&#xff0c;只能在棧上創建對象 四、請設計一個類&#xff0c;不能被繼承 五、請設計一個類&#xff0c;只能創建一個對象(單例模式) C&#x1f337; 一、設計一個類&…

Sentinel使用實例

不說了&#xff0c;直接上官方文檔 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md Sentinel Example 項目說明 本項目演示如何使用 Sentinel starter 完成 Spring Clo…

【金融量化】對企業進行估值的方法有哪些?

估值的方法有哪些&#xff1f; 如何對企業進行估值&#xff1f;有2個方法估算。 1 絕對估值法 它是一種定價模型&#xff0c;用于計算企業的內在價值。 比如說你可以根據公司近N年的現金流情況。借此去預測未來N年的現金流情況。所有的現金流數據都可以在年報上查詢到。最后…

ios 知識

IOS 類文件.h和.m中interface的區別 大家都知道我們在創建類文件時會發現&#xff1a; #import <UIKit/UIKit.h>interface ViewController : UIViewControllerend和 #import "ViewController.h"interface ViewController ()end那么他們之間有何區別呢&#x…

【Ajax】回調地獄解決方法

回調地獄&#xff08;Callback Hell&#xff09;是指在異步編程中&#xff0c;特別是在嵌套的回調函數中&#xff0c;代碼變得深度嵌套、難以閱讀和維護的現象。這通常發生在處理多個異步操作時&#xff0c;每個操作都依賴于前一個操作的結果。回調地獄使代碼變得難以理解、擴展…

顯卡服務器適用于哪些場景

顯卡&#xff08;GPU&#xff09;服務器&#xff0c;簡單來說&#xff0c;GPU服務器是基于GPU的應用于視頻編解碼、深度學習、科學計算等多種場景的快速、 穩定、彈性的計算服務。那么壹基比小鑫告訴你顯卡服務器主要的用途有哪一些。 一、運行手機模擬器 顯卡服務器可支持…

力扣:62. 不同路徑(Python3)

題目&#xff1a; 一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為 “Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為 “Finish” &#xff09;。 問總共有多少條不同的路徑&…

WebRTC音視頻通話-WebRTC本地視頻通話使用ossrs服務搭建

iOS開發-ossrs服務WebRTC本地視頻通話服務搭建 之前開發中使用到了ossrs&#xff0c;這里記錄一下ossrs支持的WebRTC本地服務搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一個簡單高效的實時視頻服務器&#xff0c;支持RTM…

STM32CubeIDE的安裝和黑色主題及自動補全代碼

STM32CubeIDE之前用過一點時間&#xff0c;但后來因為不習慣放棄了最近在新電腦上又用起來了&#xff0c;感覺相對之前好了很多&#xff0c;其實如果在工作中基本使用的是STM32,用意法的生態軟件也挺好的&#xff0c;意法最近在這塊也在大力發展&#xff0c;STM32CubeIDE安裝包…

【BASH】回顧與知識點梳理(十三)

【BASH】回顧與知識點梳理 十三 十三. 文件內容查閱13.1 直接檢視文件內容&#xff1a;cat, tac, nlcat (concatenate)tac (反向列示)nl (添加行號打印) 13.2 可翻頁檢視&#xff1a;more, lessmore (一頁一頁翻動)less (一頁一頁翻動) 13.3 資料擷取&#xff1a;head, tailhea…

【Linux】云服務器自動化部署VuePress博客(Jenkins)

前言 博主此前是將博客部署在 Github Pages&#xff08;基于 Github Action&#xff09;和 Vercel 上的&#xff0c;但是這兩種部署方式對于國內用戶很不友好&#xff0c;訪問速度堪憂。因此將博客遷移到自己的云服務器上&#xff0c;并且基于 Jenkins&#xff08;一款開源持續…

浪涌保護器中SPD防雷模塊的主要應用方案

浪涌保護器&#xff08;Surge Protective Device&#xff0c;SPD&#xff09;是一種用于限制瞬態過電壓和導引泄放電涌電流的非線性防護器件&#xff0c;用以保護耐壓水平低的電器或電子系統免遭雷擊及雷擊電磁脈沖或操作過電壓的損害。SPD可以將過電壓泄放到地線或限制過電壓到…