你真的了解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.mzph.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 竞赛信息 全国大学生数学建模…

Design-Pattern设计模式

Design-Pattern设计模式 图说设计模式 图说设计模式 在线书籍 软件模式是将模式的一般概念应用于软件开发领域&#xff0c;即软件开发的 总体指导思路或参照样板。软件模式并非仅限于设计模式&#xff0c;还包括 架构模式、分析模式和过程模式等&#xff0c;实际上&#xff…

FFmpeg常见命令行(四):FFmpeg流媒体

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

UML 类图的画法

1.类图的画法 类 整体是个矩形&#xff0c;第一层类名&#xff0c;第二层属性&#xff0c;第三层方法。 &#xff1a;public- : private# : protected空格: 默认的default 对应的类写法。 public class Student {public String name;public Integer age;protected I…

【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…

力扣: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安装包…

【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可以将过电压泄放到地线或限制过电压到…

多元最短路(Floyd)

是一个基于动态规划的全源最短路算法。它可以高效地求出图上任意两点之间的最短路 时间复杂度 O(n^3) 状态转移方程 f[i][j]min(f[i][j],f[i][k]f[k][j]) 核心代码 void floyd(){for(int k1;k<n;k)for(int i1;i<n;i)for(int j1;j<n;j)s[i][j]min(s[i][j],s[i][k…

Vue前端 更具router.js 中的meta的roles实现路由卫士,实现权限判断。

参考了之篇文章 1、我在登陆时获取到登录用户的角色名roles&#xff0c;并存入sessionStorage中&#xff0c;具体是在login页面实现&#xff0c;还是在menu页面实现都可以。在menu页面实现&#xff0c;可以显得登陆快一些。 2、编写router.js&#xff0c;注意&#xff0c;一个…

Spring 事务详解

目录 一、概述二、事务的特性&#xff08;ACID&#xff09;三、Spring 的事务管理3.1 编程式事务管理3.2 编程式事务管理 四、Spring 事务管理接口及其定义的属性4.1 PlatformTransactionManager:事务管理接口4.2 TransactionDefinition:事务属性4.3 TransactionStatus:事务状态…

Stable Diffusion+Temporal-kit 半虚半实应用

1.先下载temporal-kit,重启webui 2.下载好ffmpeg,配置好环境,下载Ebsynth 3.准备好你需要的视频,拖到预处理视频位置 4.填写参数,点解保存设置,然后并点击生成,会生成到目标文件夹的input位置 5.然后拉出input文件夹里面你想切换成处理的帧图片,然后填写prompt查看效…

中国省级、城市-数字经济创新创业、分项指数(2010-2020年)

一、数据介绍 数据名称&#xff1a;中国省级、城市-数字经济创新创业、分项指数 数据年份&#xff1a;2010-2020年 数据范围&#xff1a;31省、336个城市 数据来源&#xff1a;北大企业大数据研究中心 二、参考文献 参考文献&#xff1a; 戴若尘,王艾昭,陈斌开.中国数字…

Win10使用Guest和空密码访问共享的完整步骤

目录 前言 启动Guest 给予Guest网络权限 允许空密码登陆 启用不安全的来并登陆 总结 前言 我们经常需要使用空密码和guest账户访问Windows共享&#xff0c;因为某些设备不支持输入密码等&#xff0c;那么该如何设置呢&#xff0c;因为步骤比较固定而且繁琐&#xff0c;于…

Python小白入门:文件、异常处理和json格式存储数据

这里写自定义目录标题 所用资料 一、从文件中读取数据1.1 读取整个文件1.2 文件路径1.3 逐行读取1.4 创建一个包含文件各行内容的列表1.5 使用文件的内容1.6 包含一百万位的大型文件1.7 圆周率值中包含你的生日吗练习题 二、写入文件2.1 写入空文件2.2 写入多行2.3 附加到文件练…