ORM简介与实战:Java+MySQL ORM框架实现
1.什么是ORM
ORM(Object-Relational Mapping)框架是一种在编程中广泛使用的技术,它实现了面向对象编程语言中的对象与关系数据库之间的映射。ORM框架的目的是为了简化数据库操作,让开发者可以用更加直观和面向对象的方式来处理数据库,而不是直接编写SQL语句。这样不仅可以提高开发效率,还能减少因为直接操作数据库而产生的错误。
1.1ORM的特点
- 对象与数据库表的映射:ORM框架允许开发者定义类(对象),这些类与数据库中的表相对应。类的属性对应于表的列,对象实例对应于表中的行。
- 数据访问抽象:通过ORM框架,开发者无需直接编写SQL语句来访问数据库。相反,他们可以使用框架提供的方法或API来执行查询、更新、删除等操作。
2.ORM实战
技术栈
-
Java:作为编程语言。
-
MySQL:作为关系数据库。
-
反射:用于动态创建对象和处理对象属性。
-
自定义注解:用于标记类和属性,以便ORM框架识别。
-
泛型:增强代码的复用性和类型安全。
-
JDBC:Java数据库连接技术,用于连接和操作数据库。
步骤
2.1引入依赖jar包
在Maven项目的pom.xml
文件中添加MySQL JDBC驱动和Lombok依赖:
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--lombok用于自动生成get和set方法与构造器等-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
2.2创建db.properties属性文件
在资源目录下创建db.properties
文件,配置数据库连接信息:
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/qy174?serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
2.3创建DButil工具类
创建一个工具类DButil
,用于加载数据库连接信息并获取数据库连接:
public class DButil {
private static String driverClassName="";
private static String url="";
private static String username="";
private static String password="";
static {
try {
Properties properties=new Properties();
InputStream inputStream = ClassLoader.getSystemResourceAsStream("db.properties");
properties.load(inputStream);
driverClassName=properties.getProperty("jdbc.driverClassName");
url=properties.getProperty("jdbc.url");
username=properties.getProperty("jdbc.username");
password=properties.getProperty("jdbc.password");
} catch (IOException e) {
throw new RuntimeException("无法读取db.properties");
}
}
public static Connection getConnection() throws Exception{
Class<?> aClass = Class.forName(driverClassName);
Connection connection= DriverManager.getConnection(url,username,password);
return connection;
}
public static void closeAll(Connection connection, PreparedStatement ps, ResultSet rs){
try {
if (rs!=null){
rs.close();
}
if (ps!=null){
ps.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2.4使用自定义注解
在类上使用,目的获得表名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
String value();
}
在成员属性上使用,目的获取列名
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
String value();
}
在成员属性上使用,目的获取主键
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableId {
String value() default "id";
}
2.5定义一个管理员实体类和Dao类,用于测试
Admin实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "Admin")
public class Admin {
@TableId
private Integer id;
@TableField(value = "username")
private String userName;
private Integer password;
private String name;
private Integer gender;
@TableField(value = "createtime")
private Date createTime;
private String head;
}
AdminDao类,继承BaseDao
public class AdminDao extends BaseDao<Admin>{
}
2.6实现BaseDao类
这个类将包含执行CRUD操作的方法。
public class BaseDao<T> {
private ResultSet rs=null;
private Connection connection=null;
private PreparedStatement ps=null;
private int row= 0;
private Class<T> clazz;
/**
* 通用添加方法 insert into 表名(column...) values(值...)
* @param t 需要插入的数据对象,泛型T
* @return row 返回影响的行数
* @throws Exception 如果操作过程中出现异常,会抛出异常
*/
public int insert(T t)throws Exception{
// 初始化SQL语句,准备插入数据
StringBuffer sql=new StringBuffer("insert into ");
// 获取数据对象的类信息
Class<?> aClass = t.getClass();
// 尝试从类上获取表名注解,用于确定插入的表名
TableName tableName = aClass.getAnnotation(TableName.class);
// 默认使用类名作为表名
String name= aClass.getSimpleName();
// 如果存在表名注解,则使用注解中的表名
if (tableName!=null){
name=tableName.value();
}
// 将表名追加到SQL语句中
sql.append(name);
// 获取类的所有字段信息
Field[] declaredFields = aClass.getDeclaredFields();
// 用于存储字段名
List arrayList=new ArrayList<>();
// 用于存储字段的值
List arrayList1=new ArrayList<>();
// 遍历所有字段,处理字段名和字段值
for (Field declaredField : declaredFields) {
// 检查字段是否为ID字段,如果是则跳过
TableId tableId = declaredField.getAnnotation(TableId.class);
if (tableId!=null){
continue;
}
// 获取字段名,如果存在字段注解,则使用注解中的字段名
String field=declaredField.getName();
TableField tableField = declaredField.getAnnotation(TableField.class);
if (tableField!=null){
field=tableField.value();
}
// 将字段名追加到字段名列表中
arrayList.add(field);
// 设置字段可访问,以便获取字段值
declaredField.setAccessible(true);
// 获取字段值,并将其包装在单引号中,追加到字段值列表中
Object o = declaredField.get(t);
arrayList1.add("'"+o+"'");
}
// 将字段名列表转换为SQL语句中的格式
String replace = arrayList.toString().replace("[", "(").replace("]", ")");
// 将字段值列表转换为SQL语句中的格式
String replace1 = arrayList1.toString().replace("[", "(").replace("]", ")");
// 将字段名和字段值追加到SQL语句中
sql.append(replace);
sql.append(" values ");
sql.append(replace1);
// 尝试执行插入操作
try {
// 获取数据库连接
connection= DButil.getConnection();
// 准备SQL语句
ps=connection.prepareStatement(sql.toString());
// 执行插入操作,返回影响的行数
row = ps.executeUpdate();
} catch (Exception e) {
// 如果出现异常,抛出运行时异常
throw new RuntimeException(e);
} finally {
// 关闭数据库连接及相关资源
DButil.closeAll(connection,ps,rs);
}
// 返回影响的行数
return row;
}
/**
* 通过反射机制获取类的字段信息,构造更新语句,然后执行更新操作。
* 通用修改方法 update 表名 set column=value,...,column=value where 主键=value
* @param t 要更新的对象,必须是注解了@TableName和@TableField的类的实例。
* @return row 返回影响的行数。
* @throws Exception 如果操作过程中出现异常,则抛出。
*/
public int update(T t) throws Exception{
// 初始化SQL语句,开始构建更新语句。
StringBuffer sql=new StringBuffer("update ");
// 获取对象的类,用于后续获取类的注解和字段信息。
Class<?> aClass = t.getClass();
// 尝试从类上获取@TableName注解,用于获取表名。
TableName tableName = aClass.getAnnotation(TableName.class);
// 获取类的简单名称,作为默认表名。
String name= aClass.getSimpleName();
// 如果类上有@TableName注解,则使用注解的值作为表名。
if (tableName!=null){
name=tableName.value();
}
// 构建SQL语句的更新部分。
sql.append(name+ " set ");
// 获取类的所有声明字段,用于后续遍历构建更新语句的字段部分。
Field[] declaredFields = aClass.getDeclaredFields();
// 初始化WHERE子句的起始部分。
String where=" where ";
// 遍历所有字段,构建更新语句的字段部分和WHERE子句。
for (Field declaredField : declaredFields) {
// 获取字段名。
// 属性名
String field = declaredField.getName();
// 尝试从字段上获取@TableField注解,用于获取字段在数据库中的名称。
TableField tableField = declaredField.getAnnotation(TableField.class);
// 尝试从字段上获取@TableId注解,用于识别主键字段。
TableId tableId = declaredField.getAnnotation(TableId.class);
// 设置字段可访问,以读取字段值。
declaredField.setAccessible(true);
// 获取字段的值。
Object value = declaredField.get(t);
// 如果字段是主键字段,则构建WHERE子句的一部分。
// 如果为主键注解
if(tableId!=null){
String tableIdName=tableId.value();
where+=tableIdName+"='"+value+"'";
continue;
}
// 如果字段上有@TableField注解,则使用注解的值作为数据库中的字段名。
if(tableField!=null){
field = tableField.value();
}
// 构建更新语句的字段部分。
sql.append(field+"='"+value+"',");
}
// 删除最后一个逗号,完成更新语句的构建。
sql.deleteCharAt(sql.length()-1).append(where);
// 从DButil中获取数据库连接。
// 执行sql语句
Connection conn = DButil.getConnection();
// 准备执行更新语句。
PreparedStatement ps = conn.prepareStatement(sql.toString());
// 执行更新操作,返回影响的行数。
int row = ps.executeUpdate();
// 返回影响的行数。
return row;
}
/**
* 构造方法
*/
public BaseDao(){
//this表示子类Dao对象
Class<? extends BaseDao> aClass = this.getClass();
//获取当前子类的父类的反射类
ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
//获取该反射类中的泛型类型
Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
clazz = (Class) actualTypeArgument;
}
/**
* 根据ID删除数据库中的记录。
* 删除通用方法 delete from 表名 where 主键=value
* @param id 主键值,用于确定要删除的记录。
* @return 返回受影响的行数,即被删除的行数。
* @throws Exception 如果数据库操作过程中发生错误,则抛出异常。
*/
public int delete(Object id) throws Exception{
// 初始化SQL语句,开始构建删除语句
StringBuffer sql=new StringBuffer("delete from ");
// 获取实体类的简单类名,用于确定表名
String tableName=clazz.getSimpleName();//实体类的名称
// 查找类上的TableName注解,以获取可能指定的表名
TableName annotation = clazz.getAnnotation(TableName.class);
// 如果找到了TableName注解,则使用注解中指定的表名
if(annotation!=null){
tableName = annotation.value();
}
// 在SQL语句中添加表名
sql.append(tableName+" where ");
// 遍历类中所有声明的字段,寻找带有TableId注解的字段,以确定主键字段
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field:declaredFields){
TableId tableId = field.getAnnotation(TableId.class);
// 如果找到了TableId注解,则确定了主键字段,构建完整的SQL语句
if(tableId!=null){
sql.append(tableId.value()+"="+id);
break;
}
}
// 从DButil中获取数据库连接
//执行sql语句
Connection conn = DButil.getConnection();
// 根据构建的SQL语句创建PreparedStatement对象
PreparedStatement ps = conn.prepareStatement(sql.toString());
// 执行更新操作,删除记录
int row = ps.executeUpdate();
// 返回受影响的行数
return row;
}
}
2.7简单测试
使用juit测试
public class Test01 {
AdminDao adminDao=new AdminDao();
@Test
public void testInsert() throws Exception{
SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = time.format(now);
LocalDate ld=LocalDate.parse(formattedDate);
Admin admin=new Admin(23,"f",123,"范",1,java.sql.Date.valueOf(ld),"hhh");
adminDao.update(admin);
}
@Test
public void testDelete() throws Exception{
adminDao.delete(23);
}
}
以上步骤为构建ORM框架的基础,接下来可以进一步实现ORM的核心功能,如对象与表的映射、SQL语句的自动生成与执行等。由于篇幅限制,这里仅展示了数据库连接部分的实现。在实际项目中,还需要结合反射、自定义注解等技术来完成完整的ORM框架开发。