什么是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技术的详尽文章,让您能够深入了解该领域的各种技巧和方法,让我们一起成为更优秀的程序员👩💻👨💻!