什么是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等方法,實現對數據庫表的增、刪、改、查操作。
在這段代碼中主要包括以下內容:
-
通過反射獲取類的屬性、方法和注解等信息;
-
通過PreparedStatement實現對數據庫的增、刪、改、查等操作;
-
對Java泛型機制的運用,如在save、update、findById等方法中,將類名和主鍵值等作為方法參數,以達到通用的效果;
-
對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技術的詳盡文章,讓您能夠深入了解該領域的各種技巧和方法,讓我們一起成為更優秀的程序員👩?💻👨?💻!