手写Java ORM编程:实现高效数据库操作的最佳实践

1. 引入相关依赖

<dependencies>
    <!--引入lombok依赖-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.30</version>
    </dependency>
    <!--引入单元测试依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--引入mysql依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
    <!--引入德鲁伊得依赖-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>
    <!--单元测试依赖-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
<!--      <scope>test</scope>-->
    </dependency>
  </dependencies>

2. 准备工具类Util

① 使用JDBC获取properties文件

注意:

在使用jdbc编程时,编写的属性文件中最好以jdbc.xxx的方式命名

如:jdbc.username:root

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @program: ORM
 * @description: 没有使用连接池的数据库工具类
 * @author: 
 * @create: 2024-07-01 19:56
 **/
public class DButil {

    private static String driverName = "";
    private static String url = "";
    private static String username = "";
    private static String password = "";

    //静态代码块
    static {
        try {
            //读取属性文件
            InputStream inputStream = ClassLoader.getSystemResourceAsStream("db.properties");
            //创建属性类  用于读取属性
            Properties properties = new Properties();
            properties.load(inputStream);
            driverName = properties.getProperty("jdbc.driverName");
            url = properties.getProperty("jdbc.url");
            username = properties.getProperty("jdbc.username");
            password = properties.getProperty("jdbc.password");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //获取数据库连接
    public static Connection getConnection(){
        Connection connection = null;
        try {
            Class.forName(driverName);
            connection = DriverManager.getConnection(url,username,password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    //关闭所有连接
    public static void closeAll(Connection connection, PreparedStatement ps, ResultSet rs){
        try {
            if(connection != null){
                connection.close();
            }
            if (ps != null){
                ps.close();
            }
            if (rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

② 使用数据源获取properties文件

注意:

在使用数据源的情况下,例如本次使用的德鲁伊数据源,在编写属性文件时,就不需要再加 jdbc.

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * @program: ORM
 * @description: 使用了连接池的数据库工具类
 * @author: 
 * @create: 2024-07-02 09:19
 **/
public class DbUtilDataSource {
    protected static Connection connection;
    protected static PreparedStatement ps;
    protected static ResultSet rs;
    protected static DataSource dataSource;

    static {
        try {
            Properties properties = new Properties();
            InputStream resourceAsStream = 	
            DbUtilDataSource.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(resourceAsStream);
            dataSource= DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        try {
            connection = dataSource.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    public static void closeAll(Connection connection, PreparedStatement ps, ResultSet rs) {
        try {
            if (connection != null) {
                connection.close();
            }
            if (ps != null) {
                ps.close();
            }
            if (rs != null) {
                rs.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 自定义注解

① 定义表名和实体类名的关系

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 {
    String value();
}

② 定义列名和实体类属性名的关系

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 ColumnName {
    String value();
}

③ 定义id自增的列

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 定义id自增的列
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnId {
    String value();
}

4. 编写baseDao

使用泛型的方式“T”获取子类对象

public class BaseDao<T> {}

① insert方法

//添加功能
    public int add(T t) {
        //拼接insert的sql语句--insert into 表名(字段名) values(值)

        /**
         * 拼接第一步:insert into
         */
        StringBuilder sql = new StringBuilder("insert into ");
        //获取表名--表名和实体类名一致;表名和实体类名不一致(使用注解获取)
        //根据对象获取反射类
        Class<?> aClass = t.getClass();
        //获取注解对象--如果表名和实体类名不一致,则使用注解获取
        TableName table = aClass.getAnnotation(TableName.class);

        /**
         * 拼接第二步:表名
         */
        //定义表名
        String tableName = "";
        if (table != null) {
            tableName = table.value();
        } else {
            tableName = aClass.getSimpleName();
        }
        sql.append(tableName);

        /**
         * 拼接第三步:字段名 values 值
         */
        //获取该类中所有的属性名
        Field[] fields = aClass.getDeclaredFields();
        //定义一个集合来存储所有列名
        List<String> columnNames = new ArrayList<String>();
        //定义一个集合存储values值
        List<Object> values = new ArrayList<Object>();

        //逐个获取所有属性名,并添加到集合当中
        for (Field field : fields) {
            //设置私有属性可访问规则
            field.setAccessible(true);

            //考虑列名和属性名不一致的情况,不一致时使用注解标注
            ColumnName column = field.getAnnotation(ColumnName.class);
            //考虑列名为自增时,不用获取情况
            ColumnId columnId = field.getAnnotation(ColumnId.class);
            //定义列名
            String columnName = "";
            if (columnId != null) {
                //如果该属性是自增列,则跳过
                continue;
            } else if (column != null) {
                //如果不一致,把注解标注的列名添加到sql语句中
                columnName = column.value();
            } else {
                //如果一致,则直接获取类的属性名
                columnName = field.getName();
            }

            try {
                //把列名存储到集合中
                columnNames.add(columnName);
                //获取属性值
                Object value = field.get(t);
                //把属性值存储到集合中
                values.add("'" + value + "'");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        //返回该集合的字符串形式,并将【】换成()
        String colNames = columnNames.toString().replace("[", "(").replace("]", ")");
        String vals = values.toString().replace("[", "(").replace("]", ")");
        sql.append(colNames);
        sql.append(" values");
        sql.append(vals);

        System.out.println(sql.toString());

        /**
         * 最后一步!执行sql语句
         */int row = 0;
        try {
            //获取连接
            Connection connection = DButil.getConnection();
            //预执行sql语句
            PreparedStatement ps = connection.prepareStatement(sql.toString());
            //执行sql语句获取返回值
            row = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return row;
    }

② update方法

//修改功能 update 表名 set 字段名=值 where id=?
    public int update(T t) {
        StringBuilder updateSql = new StringBuilder("update ");
        String where = " where ";
        //获取反射类
        Class<?> aClass = t.getClass();
        //获取表名
        String tableName = "";
        TableName table = aClass.getAnnotation(TableName.class);
        if (table != null) {
            tableName = table.value();
        } else {
            tableName = aClass.getSimpleName();
        }
        updateSql.append(tableName + " set ");

        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            //设置私有属性可访问规则
            field.setAccessible(true);
            Object value = null;
            try {
                //获取属性值
                value = field.get(t);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            //获取列名和自增id
            String columnName = "";
            ColumnId columnId = field.getAnnotation(ColumnId.class);
            ColumnName column = field.getAnnotation(ColumnName.class);
            if (columnId != null) {
                //如果是主键id,拼接where条件
                String columnIdName = columnId.value();
                where += (columnIdName + "=" + "'" + value + "'");
                continue;
            } else if (column != null) {
                columnName = column.value();
            } else {
                columnName = field.getName();
            }

            updateSql.append(columnName + "=" + "'" + value + "',");

        }
        //删除最后一个逗号
        updateSql.deleteCharAt(updateSql.length() - 1);
        //System.out.println("不带where的update:" + updateSql.toString());

        //添加where
        updateSql.append(where);

        //System.out.println("where:"+where);
        //System.out.println("带where的update:" + updateSql.toString());

        /**
         * 最后一步!执行sql语句
         */int row = 0;
        try {
            //获取连接
            Connection connection = DButil.getConnection();
            //预执行sql语句
            PreparedStatement ps = connection.prepareStatement(updateSql.toString());
            //执行sql语句获取返回值
            row = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return row;
    }

③ delete方法

难点解析

– 删除功能根据实际场景常常需要传入一个id值或者关键信息来进行删除,而不是传入整个整个实体对象。

– 所以在这里就不能再采取传入泛型T t的方式,选用传入一个object类型的参数

– 但是问题就来了,之前传入的是泛型T t,创建studentDao,则t对应便是studentDao,然后根据t来获取反射类,再获取列名,字段名等信息。而现如今入的是一个object对象,所以无法获取反射类

解决:

定义一个构造方法,在basedao加载时,该构造方法便会执行

在该构造方法中

  1. this指代的是继承baseDao的子类,通过".getClass()"获取到子类(user啊,student啊等)的反射类

  2. 再通过".getGenericSuperclass()"获取到父类(BaseDao)

  3. 最后通过".getActualTypeArguments()"的方式,获取到父类的参数列表,即我们想要的泛型

//定义当前对象的反射类
    private Class<T> clazz;
    //定义构造方法 随着类的加载而加载--获取子类dao继承basedao的泛型
    public BaseDao(){
        //获取当前子类的反射类 this表示子类Dao对象
        Class<? extends BaseDao> aClass = this.getClass();
        //调用getGenericSuperclass()方法获取当前子类的父类的反射类
        //父类的返回类型为type,要用到其子类的方法,所以强转为ParameterizedType
        ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
        //获取该反射类中的泛型类型
        Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];

        clazz= (Class) actualTypeArgument;
    }
delete方法
//删除功能 delete from 表名 where 主键列名=id
public int deleteByid(Object id){
        //1. 定义delete from
        StringBuffer deleteSql=new StringBuffer("delete from ");

        //2. 获取表名
        String tableName=clazz.getSimpleName();
        /**
         * 考虑表名和实体类不一致的情况,给不一致的实体类上加注解,定义数据库表的真实表名
         */
        TableName annotation = clazz.getAnnotation(TableName.class);
        if(annotation!=null){
            tableName = annotation.value();
        }

        //3. 拼接where条件
        deleteSql.append(tableName+" where ");

        //4. 获取where条件列名(常常为主键id)
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field:declaredFields){
            /**
             * 考虑主键id是自增情况,所以常常加了id注解。通过注解的方式直接获取到id值,然后拼接,break结束循环。
             */
            ColumnId tableId = field.getAnnotation(ColumnId.class);
            if(tableId!=null){
                deleteSql.append(tableId.value()+"="+"'"+id+"'");
                break;
            }
        }
        System.out.println("deleteSql:"+deleteSql);

        /**
         * 最后一步!执行sql语句
         */
        int row = 0;
        try {
            //获取连接
            Connection connection = DButil.getConnection();
            //预执行sql语句
            PreparedStatement ps = connection.prepareStatement(deleteSql.toString());
            //执行sql语句获取返回值
            row = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return row;
    }

④ 根据id查询

//根据id查询
    //sql语句:select * from 表名 where id=值
    public T getById(Object id) throws Exception {
        //sql语句
        StringBuilder sql = new StringBuilder("select * from ");
        //获取表名
        String tableName = clazz.getSimpleName();
        //考虑实体类名与表名不一致的情况
        //注解获取表名
        TableName annotation = clazz.getAnnotation(TableName.class);
        if (annotation != null) {
            tableName = annotation.value();
        }
        sql.append(tableName + " where ");
        //获取属性名称
        Field[] declaredFields = clazz.getDeclaredFields();
        //循环遍历
        for (Field f : declaredFields) {
            //获取主键
            ColumnId tableId = f.getAnnotation(ColumnId.class);
            if (tableId != null) {
                //获取属性名称
                String value = tableId.value();
                //追加到sql语句中
                sql.append(value + "='" + id + "'");
                //跳出循环
                break;
            }
        }
        //执行sql语句
        Connection con = DButil.getConnection();
        //获取执行sql语句的对象
        PreparedStatement ps = con.prepareStatement(sql.toString());
        //执行sql语句,获取查询的结果集
        ResultSet rs = ps.executeQuery();
        //通过泛型实例化一个当前子类对象,接下来循环遍历,为对象赋值
        T t = clazz.newInstance();
        while (rs.next()) {
            //遍历循环属性名称
            for (Field f : declaredFields) {
                // 暴力访问私有属性
                f.setAccessible(true);
                //处理主键
                ColumnId an_ID = f.getAnnotation(ColumnId.class);
                if (an_ID != null) {
                    //如果获取到了主键,则将主键值赋值给对象
                    f.set(t, rs.getObject(an_ID.value()));
                } else {
                    //处理其他属性
                    ColumnName an_columnName = f.getAnnotation(ColumnName.class);
                    if (an_columnName != null) {
                        f.set(t, rs.getObject(an_columnName.value()));
                    } else {
                        f.set(t, rs.getObject(f.getName()));
                    }
                }
            }
        }
        //System.out.println(sql);

        return t;
    }

⑤ 查询所有

//查询所有
    public List<T> getAll() throws Exception {
        //定义集合,存储查询结果
        List<T> list=new ArrayList<T>();
        //sql语句
        StringBuilder sql = new StringBuilder("select * from ");
        //获取表名
        String tableName = clazz.getSimpleName();
        //考虑实体类名与表名不一致的情况
        //注解获取表名
        TableName an_tableName = clazz.getAnnotation(TableName.class);
        if (an_tableName != null) {
            tableName = an_tableName.value();
        }
        sql.append(tableName);
        //执行sql语句
        Connection con = DButil.getConnection();
        PreparedStatement ps = con.prepareStatement(sql.toString());
        ResultSet rs = ps.executeQuery();
        while (rs.next()){
            T t=clazz.newInstance();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field:declaredFields){
                field.setAccessible(true);
                ColumnId an_columnId = field.getAnnotation(ColumnId.class);
                if(an_columnId!=null){
                    field.set(t,rs.getObject(an_columnId.value()));
                }else{
                    ColumnName an_columnName = field.getAnnotation(ColumnName.class);
                    if(an_columnName!=null){
                        field.set(t,rs.getObject(an_columnName.value()));
                    }else{
                        field.set(t,rs.getObject(field.getName()));
                    }
                }
            }
            list.add(t);
        }
        return list;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值