ORM框架入门

ORM框架

手写ORM框架: O(Object 对象) R(Relative 关系) M(Mapping 映射) 对象关系映射。把数据中的表映射为java中的实体类,表中的一条记录映射为java实体类对象。表中的列映射实体的属性了。

之前我们学习过一个ORM---Mybatis.-----单表的CRUD无需写任何sql语句。

我们自己手写的ORM框架使用的技术: 泛型 注解 反射。

反射: 把类中成员抽取为其他类对象的过程。就是反射。

反射类: Class--->实例化类对象。

获取属性类对象: Field

获取方法类对象: Method

获取构造方法类对象: Constructor

(1)创建maven工程并引入相关依赖

 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.31</version>
 </dependency>

(2)创建一个连接数据库的工具类。

db.properties

 # mysql-connector-java 6.x及后续版本中的驱动类名
 jdbc.driverName=com.mysql.cj.jdbc.Driver
 # qy65是需要连接的具体数据库名,并且设置数据库时区为亚洲上海
 jdbc.url=jdbc:mysql://localhost:3306/qy65?serverTimezone=Asia/Shanghai
 # 数据库用户名(用于连接数据库)
 jdbc.username=root
 # 用户密码(用于连接数据库)
 jdbc.password=123456

DbUtil.java

 package com.aaa.util;
 ​
 import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.util.Properties;
 ​
 /**
  * @BelongsProject:orm
  * @Description:
  * @Author: Zhou
  * @CreateTime: 2023/6/12 14:47
  */
 public class DbUtil {
     /**
      * 硬编码
      * 计算机科学中,只有硬编码(hardcode),以及非硬编码,有人也成为“软编码”。
      * 硬编码和软编码的区别是:软编码可以在运行时确定,修改;而硬编码是不能够改变的。
      */
     private static String driverName="";
     private static String url="";
     private static String username="";
     private static String password="";
     //静态代码块。随着类的加载而被加载,而且只会被加载一次
     static {
 ​
         try {
             /*
              * Properties读取配置文件属性值的方式
              * 使用Class类的getSystemResourceAsStream方法 和使用当前类的ClassLoader是一样的
              * InputStream inputStream = ClassLoader.getSystemResourceAsStream(name)
              */
             //java.lang.ClassLoader 类是负责加载类的对象。 这个类是一个抽象类。 安全管理员可以使用它来指示安全域。
             //java.lang.ClassLoader.getSystemResourceAsStream() 方法打开读取,从用于加载类的搜索路径中指定名称的资源。
             InputStream inputStream = ClassLoader.getSystemResourceAsStream("db.properties");
             //实例化Properties类
             Properties properties = new Properties();
             //调用load()方法加载properties文件,load里面传入InputSteam类型的参数或者Reader类型的参数
             properties.load(inputStream);
             //通过getProperty(String key)方法获取,传入一个String类型的键,返回一个String类型的值 如果键不存在则返回null
             driverName = properties.getProperty("jdbc.driverName");
             //获取相应的属性值
             url = properties.getProperty("jdbc.url");
             username = properties.getProperty("jdbc.username");
             password = properties.getProperty("jdbc.password");
         }catch (Exception e){
             System.out.println("名字为db.properties的属性文件不存在");
             e.printStackTrace();
         }
     }
 ​
     public static void main(String[] args) throws Exception {
         System.out.println(getConnection());
     }
 ​
     /**
      * 获取连接对象
      * @return
      * @throws Exception
      */
     public static Connection getConnection() throws Exception{
         //Class.forName()方法实现类加载
         Class.forName(driverName);
         //DiverManager.getConnection(String url,String user, String password)方法来获取连接。
         Connection connection = DriverManager.getConnection(url,username,password);
         return connection;
     }
 ​
     /**
      * 关闭资源
      * @param connection
      * @param ps
      * @param resultSet
      */
     public static void closeAll(Connection connection, PreparedStatement ps, ResultSet resultSet){
         try {
             if (resultSet!=null){
                 resultSet.close();
             }
             if (ps!=null) {
                 ps.close();
             }
             if (connection!=null) {
                 connection.close();
             }
         }catch (Exception e){
             e.printStackTrace();
         }
 ​
     }
 }

(3)创建一个父类BaseDao

一个子类DAO继承BaseDao类后,无需再写任何sql语句,而就可以完成单表的crud操作。

比如: StudentDao extends BaseDao DeptDao extends BaseDao.

BaseDao对所有表的CRUD操作都能通用。

package com.aaa.dao;
 ​
 import com.aaa.annotation.TableField;
 import com.aaa.annotation.TableId;
 import com.aaa.annotation.TableName;
 import com.aaa.util.DbUtil;
 ​
 ​
 import java.lang.reflect.Field;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * @BelongsProject:orm
  * @Description:
  * @Author: Zhou
  * @CreateTime: 2023/6/12 17:04
  */
 public class BaseDao<T> {
 ​
 ​
     Class<T> clazz;
 ​
     public BaseDao() {
         /**
          * getGenericSuperclass() 通过反射获取当前类表示的实体(类,接口,基本类型或void)的直接父类的Type,
          * getActualTypeArguments()返回参数数组。
          * getActualTypeArguments()就是获取泛型参数的类型
          */
         //获取BaseDao子类的反射类对象
         Class<? extends BaseDao> aClass = this.getClass();
         //获取当前Dao子类的父类的反射类
         ParameterizedType parameterizedType = (ParameterizedType) aClass.getGenericSuperclass();
         //得到泛型的反射类
         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
         clazz = (Class<T>) actualTypeArguments[0];
 //        System.out.println(clazz);
     }
     /**
      * 通用的查询 根据主键查询一条记录
      * SQL语句:select * from 表名 where 主键=值
      */
     public T selectById(Object id) throws Exception{
         StringBuffer sql=new StringBuffer("select * from ");
         TableName annotation = clazz.getAnnotation(TableName.class);
         String tableName=clazz.getSimpleName();
         if(annotation!=null){
             tableName=annotation.value();
         }
         sql.append(tableName+" where ");
         /**
          * getFields()和getDeclaredFields()的区别
          * getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
          * getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
          */
         Field[] fields = clazz.getDeclaredFields();
         for(Field field:fields){
             TableId tableId = field.getAnnotation(TableId.class);
             if(tableId!=null){
                 sql.append(tableId.value()+"='"+id+"'");
                 continue;
             }
         }
         System.out.println(sql);
         //执行sql语句
         Connection connection = DbUtil.getConnection();
         PreparedStatement ps = connection.prepareStatement(sql.toString());
         ResultSet rs = ps.executeQuery();
         T t = null;
         while (rs.next()){
             t= clazz.newInstance();
             Field[] declaredFields = clazz.getDeclaredFields();
             for(Field field:declaredFields){
                 field.setAccessible(true);
                 String name = field.getName();
                 TableField tableField = field.getAnnotation(TableField.class);
                 TableId tableId = field.getAnnotation(TableId.class);
                 if(tableField!=null){
                     name=tableField.value();
                 }
                 if(tableId!=null){
                     name=tableId.value();
                 }
                 Object v = rs.getObject(name);
                 field.set(t,v);
             }
         }
         return t;
     }
     /**
      * 通用的删除
      * SQL语句:delete from 表名 where 主键=值
      */
     public int delete(Object id) throws Exception{
         StringBuffer sql = new StringBuffer("delete from ");
         TableName annotation = clazz.getAnnotation(TableName.class);
         String tableName = clazz.getSimpleName();
         if (annotation!=null){
             tableName=annotation.value();
         }
         sql.append(tableName+" where ");
 ​
         Field[] fields = clazz.getDeclaredFields();
         for (Field field : fields) {
             TableId tableId = field.getAnnotation(TableId.class);
             if (tableId!=null){
                 sql.append(tableId.value()+"='"+id+"'");
                 /**
                  * continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环。
                  * 注意:continue语句并没有使整个循环终止。
                  */
                 continue;
             }
 ​
         }
         System.out.println(sql);
         //执行SQL语句
         Connection connection = DbUtil.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
         int i = preparedStatement.executeUpdate();
         return i;
     }
     /**
      * 通用的添加操作
      * 通用的添加SQL语句:insert into 表名(列名,列名....) values(值,值....)
      * @param t
      * @return
      */
     public int insert(T t) throws Exception{
         /*
         String:不可变字符串
         StringBuffer:可变字符串、效率低、线程安全
         StringBuilder:可变字符序列、效率高、线程不安全
          */
         //拼接SQL语句
         StringBuffer sql = new StringBuffer("insert into ");
         //根据对象获取Class反射类
         Class<?> aClass = t.getClass();
         //获取反射类上的注解对象
         TableName annotation = aClass.getAnnotation(TableName.class);
         //表名 getSimpleNam得到类的简写名称
         String tableName = aClass.getSimpleName();
         if (annotation!=null){
             tableName=annotation.value();
         }
         //append() 方法可以向列表末尾添加「任意类型」的元素
         sql.append(tableName);
         //获取列名 获取该反射类里面的所有的属性对象
         Field[] declaredFields = aClass.getDeclaredFields();
         //列名
         List<String> columns = new ArrayList<>();
         //值
         List<String> values = new ArrayList<>();
         //遍历所有的属性对象
         for (Field field : declaredFields) {
             //属性名
             String name = field.getName();
             TableId annotation2 = field.getAnnotation(TableId.class);
             if (annotation2!=null||name.equals("id")){
                 continue;
             }
 ​
             //getAnnotation()方法 返回该元素的指定类型的注释,如果是这样的注释,否则返回null。
             TableField annotation1 = field.getAnnotation(TableField.class);
             if (annotation1!=null){
                 name=annotation1.value();
             }
             //Accessable属性是继承自AccessibleObject 类. 功能是启用或禁用安全检查
             //实际上setAccessible是启用和禁用访问安全检查的开关
             field.setAccessible(true);
             Object o = field.get(t);
             columns.add(name);
             values.add("'"+o+"'");
         }
         /**
          * replace() 方法在字符串中搜索值或正则表达式。
          * replace() 方法返回已替换值的新字符串。
          * replace() 方法不会更改原始字符串。
          */
         String columnNames = columns.toString().replace("[", "(").replace("]", ")");
         String columnValues = values.toString().replace("[", "(").replace("]", ")");
         sql.append(columnNames);
         sql.append(" values ");
         sql.append(columnValues);
         System.out.println(sql);
 ​
         //执行SQL语句 Java DriverManager.getConnection() 方法用于获得试图建立到指定数据库 URL 的连接。
         Connection connection = DbUtil.getConnection();
         //PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程
         PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
         /**
          * 增、删、改 用executeUpdate()
          * 返回值为int型,表示被影响的行数
          *
          * 查用executeQuery() 返回的是一个集合
          * .next()表示 指针先下一行,还有first()指向第一行 、last()指向最后一行、如果有数据就返回true
          */
         int i = preparedStatement.executeUpdate();
         return i;
     }
     /**
      * 通用的修改方法
      * SQL语句:update 表名 set 列名=值,列名=值...where 主键名=值
      */
     public int update(T t) throws Exception{
         StringBuffer sql=new StringBuffer("update ");
         //获取实体类的反射类
         Class<?> aClass = t.getClass();
         //获取反射类指定的注解对象
         TableName annotation = aClass.getAnnotation(TableName.class);
         String tableName = aClass.getSimpleName();
         if(annotation!=null){
             tableName=annotation.value();
         }
         sql.append(tableName+" set ");
         //获取所有的Field
         Field[] declaredFields = aClass.getDeclaredFields();
         String where = " where ";
         for (Field field : declaredFields) {
             field.setAccessible(true);
             String name = field.getName();
             TableId tableId = field.getAnnotation(TableId.class);
             if (name.equals("id")){
                 where = where +" id='"+field.get(t)+"'";
                 continue;
             }
             if (tableId!=null){
                 where = where + tableId.value()+"='"+field.get(t)+"'";
             }
 ​
             TableField tableField = field.getAnnotation(TableField.class);
             if (tableField!=null){
                 name=tableField.value();
             }
 ​
             String value="'"+field.get(t)+"'";
             sql.append(name+"="+value+",");
         }
         String sql2 = sql.toString().substring(0, sql.length() - 1)+where;
         System.out.println(sql2);
         //执行SQL语句
         Connection connection = DbUtil.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(sql2);
         int i = preparedStatement.executeUpdate();
         return i;
 ​
     }
 }

(4)继承父类的子类

DeptDao.java

package com.aaa.dao;
 ​
 import com.aaa.pojo.Dept;
 ​
 /**
  * @BelongsProject:orm
  * @Description:
  * @Author: Zhou
  * @CreateTime: 2023/6/12 17:56
  */
 public class DeptDao extends BaseDao<Dept>{
 }

EmpDao.java

package com.aaa.dao;
 ​
 import com.aaa.pojo.Emp;
 ​
 /**
  * @BelongsProject:orm
  * @Description:
  * @Author: Zhou
  * @CreateTime: 2023/6/12 17:55
  */
 public class EmpDao extends BaseDao<Emp> {
 }

(5)创建实体类

Emp.java

package com.aaa.pojo;
 ​
 import com.aaa.annotation.TableField;
 import com.aaa.annotation.TableId;
 ​
 public class Emp {
     @TableId
     private int id;
     private String ename;
     private String sex;
     private double salary;
     @TableField(value = "dept_id")
     private int deptId;
 ​
     @Override
     public String toString() {
         return "Emp{" +
                 "id=" + id +
                 ", ename='" + ename + '\'' +
                 ", sex='" + sex + '\'' +
                 ", salary=" + salary +
                 ", deptId=" + deptId +
                 '}';
     }
 ​
     public Emp() {
     }
 ​
     public Emp(int id, String ename, String sex, double salary, int deptId) {
         this.id = id;
         this.ename = ename;
         this.sex = sex;
         this.salary = salary;
         this.deptId = deptId;
     }
 ​
     public int getId() {
         return id;
     }
 ​
     public void setId(int id) {
         this.id = id;
     }
 ​
     public String getEname() {
         return ename;
     }
 ​
     public void setEname(String ename) {
         this.ename = ename;
     }
 ​
     public String getSex() {
         return sex;
     }
 ​
     public void setSex(String sex) {
         this.sex = sex;
     }
 ​
     public double getSalary() {
         return salary;
     }
 ​
     public void setSalary(double salary) {
         this.salary = salary;
     }
 ​
     public int getDeptId() {
         return deptId;
     }
 ​
     public void setDeptId(int deptId) {
         this.deptId = deptId;
     }
 }

Dept.java

package com.aaa.pojo;
 ​
 import com.aaa.annotation.TableField;
 import com.aaa.annotation.TableId;
 import com.aaa.annotation.TableName;
 ​
 @TableName(value = "tbl_dept")
 public class Dept {
     @TableId(value = "did")
     private int did;
     @TableField(value = "d_name")
     private String name;
     private String addr;
 ​
     @Override
     public String toString() {
         return "Dept{" +
                 "did=" + did +
                 ", name='" + name + '\'' +
                 ", addr='" + addr + '\'' +
                 '}';
     }
 ​
     public int getDid() {
         return did;
     }
 ​
     public void setDid(int did) {
         this.did = did;
     }
 ​
     public String getName() {
         return name;
     }
 ​
     public void setName(String name) {
         this.name = name;
     }
 ​
     public String getAddr() {
         return addr;
     }
 ​
     public void setAddr(String addr) {
         this.addr = addr;
     }
 }

(6)注解类

TableField.java

 package com.aaa.annotation;
 ​
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 ​
 //注解运用在属性上
 @Target(ElementType.FIELD)
 //运行时有效
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TableField {
     String value();
 }

TableId.java

package com.aaa.annotation;
 ​
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 ​
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TableId {
     String value() default "id";
 }

TableName.java

 package com.aaa.annotation;
 ​
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 ​
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface TableName {//在实体类标记对应的表名
    /**
     * Target 是目标的意思,@Target 指定了注解运用的地方。
     * ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
     * ElementType.CONSTRUCTOR 可以给构造方法进行注解
     * ElementType.FIELD 可以给属性进行注解
     * ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
     * ElementType.METHOD 可以给方法进行注解
     * ElementType.PACKAGE 可以给一个包进行注解
     * ElementType.PARAMETER 可以给一个方法内的参数进行注解
     * ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
     */
    /**
     *Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
     * 它的取值如下:
     * RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
     * RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
     * RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
     * 我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。
     * @Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
     */
    String value();
 ​
 }

(7)创建测试类

package com.aaa;
 ​
 import com.aaa.dao.DeptDao;
 import com.aaa.dao.EmpDao;
 import com.aaa.pojo.Dept;
 import com.aaa.pojo.Emp;
 ​
 public class Test {
     public static void main(String[] args) throws Exception {
         DeptDao deptDao=new DeptDao();
         System.out.println(deptDao.selectById(2));
 //        deptDao.delete(1);
 //        Dept dept = new Dept();
 //        dept.setName("人事部");
 //        dept.setAddr("郑州");
 //        dept.setDid(1);
 //        deptDao.update(dept);
 //        deptDao.insert(dept);
 ​
 ​
         EmpDao empDao=new EmpDao();
         System.out.println(empDao.selectById(9));
 //        empDao.delete(5);
 //        Emp emp = new Emp(9,"王啟光","男",5000,3);
 //        empDao.update(emp);
 //        Emp emp = new Emp(0,"王振阳","男",82,2);
 //        empDao.insert(emp);
     }
 }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值