自定義ORM框架

李松燃發表於2024-07-02

手撕ORM框架

1.建立Maven工程

image-20240702211254563

2.匯入依賴

    <dependencies>
        <!-- 引入 jdbc 的依賴 -->
        <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

3.設計運算元據庫的工具類

public class DbUtil {
    // 德魯伊連線池
    private static DataSource dataSource;

    // 使用靜態程式碼塊載入配置檔案
    static{
        try{
            // 建立一個屬性物件
            Properties properties = new Properties();
            // 載入屬性檔案
            InputStream inputStream = DbUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(inputStream);
            // 獲取連線池物件
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e){
            e.printStackTrace();
        }
    }

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

    public static void closeAll(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(preparedStatement != null){
            try {
                preparedStatement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

4.建立配置檔案 db.properties

#資料來源資訊
url=jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai
username=root
password=123456
driverClass=com.mysql.cj.jdbc.Driver

5.設計自定義註解

// 修飾表名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
    String value();
}
// 修飾主鍵Id
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableId {
    String value() default "id";
}
// 修飾表欄位
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableField {
    String value();
}

6.實體類中使用自定義註解

@TableName("表名")
@Data
public class Student {
    @TableId("主鍵名")
    private Integer id;
    @TableField("列名")
    private String name;
    @TableField("列名")
    private Integer age;
}

7.設計BaseDao

public class BaseDao <T> {

    private Class<T> clazz;

    public BaseDao(){
        // this表示子類 Dao物件
        Class<? extends BaseDao> aClass = this.getClass();
        // 獲取當前子類的父類反射類
        ParameterizedType genericSuperclass = (ParameterizedType) aClass.getGenericSuperclass();
        // 獲取該反射類中的泛型型別
        Type actualTypeArgument = genericSuperclass.getActualTypeArguments()[0];
        // 將獲取得到的泛型型別經過強轉後重新給 clazz
        clazz = (Class) actualTypeArgument;
    }

    public int insert(T t) {
        try{
            // 設計通用的新增
            // 1.首先寫出新增的 sql insert into 表名(欄位名) values(屬性名)
            // 這裡注意 into 後面的空格
            StringBuffer sql = new StringBuffer("insert into ");
            // 2.獲取實體類所對應的表名
            // 2.1自定義註解 @TableName 標識實體類所對應的表
            // 2.2拿到實體類的反射類,利用反射獲取註解,進而獲得表的名稱
            Class clazz = t.getClass();
            // 實體類的名稱
            String simpleName = clazz.getSimpleName();
            // 表的名稱
            String tableName = "";
            // 獲取類上的註解
            TableName annotation = (TableName) clazz.getAnnotation(TableName.class);
            if(annotation != null){
                tableName = annotation.value();
            }
            // 追加拿到的表名
            sql.append(tableName);
            // 3.獲取所有的列名和對應的屬性名
            // 3.1用來儲存資料庫中對應的列
            List<String> columnNames = new ArrayList<String>();
            // 3.2用來儲存需要插入的值
            List<Object> values = new ArrayList<Object>();
            // 4.獲取所有的屬性物件
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 4.1獲取類中的屬性名
                String name = field.getName();
                // 4.2獲取屬性上的註解
                // 獲取主鍵的註解
                TableId tableId = field.getAnnotation(TableId.class);
                // 獲取成員的註解
                TableField tableField = field.getAnnotation(TableField.class);
                // 4.3根據拿到的值進行過濾
                if(tableId != null){
                    // 4.3.1 此時拿到的是主鍵的註解 退出本輪迴圈
                    continue;
                }
                if(tableField != null){
                    // 4.3.2 此時拿到的是成員的註解
                    // 覆蓋掉類中的屬性名 使用表中的欄位名
                    name = tableField.value();
                }
                // 對私有屬開啟暴力反射
                field.setAccessible(true);
                // 獲取物件屬性對應的值
                Object value = field.get(t);
                // 將值經過新增引號後加入到值list中
                values.add("'"+value+"'");
                // 將屬性列加入到列list中
                columnNames.add(name);
            }
            // 5.將列list轉換成字串,然後使用字串替換方法將其轉換成需要的格式
            String columnList = columnNames.toString().replace("[","(").replace("]", ")");
            // 6.將值list轉換成字串,然後使用字串替換方法將其轉換成需要的格式
            String valueList = values.toString().replace("[","(").replace("]",")");
            // 7.合併字串
            sql.append(columnList + " values " + valueList);
            System.out.println(sql);
            // 8.執行插入語句
            Connection connection = DbUtil.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
            int i = preparedStatement.executeUpdate();
            return i;
        } catch (Exception e){
            e.printStackTrace();
        }
        return 0;
    }

    public int update(T t){
        try{
            //設計通用的修改
            // 1.設計 sql update 表名 set 屬性名=值,...
            StringBuffer sql = new StringBuffer("update ");
            // 2.獲取表名
            // 2.1獲取物件的反射類例項
            Class clazz = t.getClass();
            // 2.2拿到物件對應的表名 類的簡名
            String tableName = clazz.getSimpleName();
            TableName tableNameAnnotation = (TableName) clazz.getAnnotation(TableName.class);
            if (tableNameAnnotation != null){
                // 2.3拿到表的名稱 使用註解的名稱進行覆蓋
                tableName = tableNameAnnotation.value();
            }
            // 3.將拿到的表名追加到sql後面
            sql.append(tableName + " set ");
            // 4.設定條件部分
            String where = "where ";
            // 5.獲取所有的列物件
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                // 5.1獲取所有的屬性名
                String name = field.getName();
                // 5.2獲取屬性上的註解
                // 5.2.1獲取主鍵的註解
                TableId tableId =(TableId) field.getAnnotation(TableId.class);
                // 5.2.2獲取屬性的註解
                TableField tableField =(TableField) field.getAnnotation(TableField.class);
                // 6.設定私有屬性允許訪問
                field.setAccessible(true);
                // 7.獲取屬性的值
                Object value = field.get(t);
                // 根據註解的種類進行選擇性拼接
                if(tableId != null){
                    String id = tableId.value();
                    where += id + "='" + value + "'";
                    // 拼接完成退出本輪迴圈
                    continue;
                }
                if(tableField != null){
                    name = tableField.value();
                }
                sql.append(name + "='" + value + "',");
            }
            // 去除 sql 中最後一個 ,
            sql.deleteCharAt(sql.length() - 1).append(where);
            System.out.println(sql);
            // 執行 sql 語句
            Connection connection = DbUtil.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
            int i = preparedStatement.executeUpdate();
            return i;
        } catch (Exception e){
            e.printStackTrace();
        }
        return 0;
    }

    public int deleteById(Object id){
        try {
            // 設計刪除的 sql 語句
            StringBuffer sql = new StringBuffer("delete from ");
            // 獲取實體類的名稱
            String tableName = clazz.getSimpleName();
            // 獲取表名的註解
            TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
            // 判斷是否新增了@TableName註解
            if(tableNameAnnotation != null){
                tableName = tableNameAnnotation.value();
            }
            // 追加 where條件
            sql.append(tableName + " where ");
            // 獲取所有的屬性物件
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                TableId tableId =(TableId) field.getAnnotation(TableId.class);
                if(tableId != null){
                    sql.append(tableId.value() + "=" + id);
                    // 跳出迴圈
                    break;
                }
            }
            // 執行 sql
            Connection connection = DbUtil.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
            int i = preparedStatement.executeUpdate();
            return i;
        } catch (Exception e){
            e.printStackTrace();
        }
        return 0;
    }

}

8.使用者定義Dao使用BaseDao

public class StudentDao extends BaseDao <Student>{}
public class TeacherDao extends BaseDao <Teacher>{}

9.測試

public class Test {
    @org.junit.Test
    public void test01(){
        // 測試新增
        StudentDao studentDao = new StudentDao();
        Student student = new Student();
        student.setId(1);
        student.setName("李白");
        student.setAge(18);
        studentDao.insert(student);
    }
    @org.junit.Test
    public void test02(){
        // 測試更新
        StudentDao studentDao = new StudentDao();
        Student student = new Student();
        student.setId(1);
        student.setName("李白");
        student.setAge(25);
        studentDao.update(student);
    }
    @org.junit.Test
    public void test03(){
        // 測試刪除
        StudentDao studentDao = new StudentDao();
        Student student = new Student();
        student.setId(1);
        studentDao.deleteById(student.getId());
    }
}

相關文章