java springboot反射實現數據庫操作mybatis

前言:

開發操作數據庫,對於SQL語句是手寫。覺得麻煩,有的簡單的也需要自己寫多次

已知操作數據庫簡便的有
一:通過generator的數據庫生成表,會生成對應的數據庫單表的操作。

二:通過jpa的方法操作數據庫

三:通過自己來寫

generator 遇到重構的情況就很難受,並且要避免多次執行 會覆蓋原本的Model、DAO和映射文件的文件夾(踩過的坑)。。

jpa相對和mybatis的優劣這個問題我表示沒權威發言權,網上很多,更偏向於mybatis,小項目爲了效率選擇jpa

開始:

1、創建項目

2、啓動類加註解 @MapperScan 掃描mapper層 

3、建一個公共的mapper 裏面有單表的 增、刪、改、查的操作

這裏就一個添加方法,然後可能有人不理解在 mapper上爲啥沒有@Mapper註解

@Mapper 是 Mybatis 的註解,和 Spring 沒有關係,@Repository 是 Spring 的註解,用於聲明一個 Bean

@Mapper 一定要有,否則 Mybatis 找不到 mapper。

@Repository 可有可無,可以消去依賴注入的報錯信息。

@MapperScan 可以替代 @Mapper。

用了@MapperScan 可以消除依賴注入的紅線報錯也不用在加@Mapper,所以直接在啓動類加了@MapperScan

4、創建mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.test.springboot.mapper.BaseMapper" >

    <insert id="shareInsert" parameterType="string" useGeneratedKeys="true" keyProperty="testId" keyColumn="id">
        ${sql}
    </insert>

</mapper>

5、建一個公共的service 同樣有 增、刪、改、查的接口,只是類上加上泛型

6、新增兩個自定義註解 PS:針對數據庫與實體類的表名或字段名不一樣使用

第一個:
import java.lang.annotation.*;

/** 定義字段的註解 */
@Retention(RetentionPolicy.RUNTIME)

/** 該註解只能用在成員變量上 */
@Target(ElementType.FIELD)

/** 將註解包含在Javadoc中 */
@Documented
public @interface Column {
    //默認爲空
    String name() default "";
}

第二個:
package com.test.springboot.sql;

import java.lang.annotation.*;

/** 定義字段的註解 */
@Retention(RetentionPolicy.RUNTIME)

/** 表名 */
@Target(ElementType.TYPE)

/** 將註解包含在Javadoc中 */
@Documented
public @interface Table {
    //默認爲空
    String name() default "";
}

註解分爲:

/*註解級別信息
RetentionPolicy.RUNTIME:VM運行期間保留註解,可以通過反射機制讀取註解信息
RetentionPolicy.SOURCE:註解將被編譯器丟棄
RetentionPolicy.CLASS:註解在class文件中可用,但會被VM丟棄*/
/*自定義註解的使用範圍
ElementType.METHOD:方法聲明
ElementType.TYPE:類、接口(包括註解類型)或enum聲明
ElementType.CONSTRUCTOR:構造器的聲明
ElementType.FIELD:域(成員)聲明(包括enum實例)
ElementType.LOCAL_VARIABLE:局部變量聲明
ElementType.PACKAGE:包聲明
ElementType.PARAMETER:參數聲明*/

7、增加實現類impl

@Service
public class BaseServiceImpl<T,T2> implements BaseService<T,T2> {
    @Autowired
    BaseMapper testTableMapper;
    @Override
    public Long shareInsert(T t) {
        //獲取表名
        String entityName = "";
        if ( null == t.getClass().getAnnotation(Table.class) ){
            //如果沒加table自定義標籤,直接使用實體類類名做爲表名
            entityName = t.getClass().getSimpleName();
        }else{
            //反之使用自定義標籤的名稱作爲表名
            entityName = t.getClass().getAnnotation(Table.class).name();
        }
        //聲明SQL字符串並寫上固定的SQL
        StringBuilder sql = new StringBuilder("insert into "+entityName+" ");

        //獲得SQL
        Map<String,String> sqlMap = getAllPropertiesForSql(t, entityName);

        //拼接爲完整SQL
        sql.append(sqlMap.get("field")).append(sqlMap.get("value"));

        return testTableMapper.shareInsert(sql.toString());
    }

    private Map<String,String> getAllPropertiesForSql(Object t){
        Map<String,String> map = new HashMap<>();
        if(null == t) return map;
        StringBuilder filedSql = new StringBuilder("(");
        StringBuilder valueSql = new StringBuilder("value (");
        //獲取所有屬性
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 判斷該成員變量上是不是存在Column類型的註解
            if (!field.isAnnotationPresent(Column.class)) {
                continue;
            }
            Column c = field.getAnnotation(Column.class);// 獲取實例
            // 獲取元素值
            String columnName = c.name();
            // 如果未指定列名,默認列名使用成員變量名
            if ("".equals(columnName.trim())) {
                columnName = field.getName();
            }
            // 獲取對象方法名
            String methodName = getMethodNameByField("get", field.getName());
            // 獲得此方法的反射
            Method method = null;
            try {
                method = t.getClass().getDeclaredMethod(methodName);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
            // 執行方法獲取返回值
            Object value = null;
            try {
                value = method.invoke(t);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            filedSql.append(columnName + ",");
            valueSql.append("'" + value + "',");
        }
        //最後一個逗號刪除掉
        valueSql.deleteCharAt(valueSql.length() - 1);
        filedSql.deleteCharAt(filedSql.length() - 1);

        //加上結尾的反括號
        valueSql.append(") ");
        filedSql.append(") ");

        //添加到返回map中
        map.put("field",filedSql.toString());
        map.put("value", valueSql.toString());
        return map;
    }

    public static String getMethodNameByField(String type, String name) {
        return type + name.substring(0, 1).toUpperCase()
                + name.substring(1, name.length());
    }

8、創建公共的控制類(或者不創建,直接使用,我沒創建,測試直接用)

@RestController
public class testController {

    @Autowired
    BaseServiceImpl<testTable,Long> baseService;

    @RequestMapping(value = "/test",method = RequestMethod.GET)
    public void test(){
        testTable testTable = new testTable();
        testTable.setName("測試2");
        testTable.setTestValue("1234567");
        Long aLong = baseService.shareInsert(testTable);
    }
}

添加返回的添加的id;

PS:這裏有注意的是,當遇到時間時,第七步裏面的設置value需要做一下轉換。

if ("Date".equals(field.getType().getSimpleName())) {
                valueSb.append("'" + DateUtil.getDfStr4Date((Date) value) + "',");
            } else {
                valueSb.append("'" + value + "',");
            }

這種有點問題,因爲是動態生成SQL,可能會有SQL注入。然後其他的刪,改,查都是這種套路。

簡單的操作套路瞭解了,其他的連表,以及查詢的函數等等,需要進一步瞭解以及編寫,暫時記錄一版簡單的避免忘掉。

對於這種個人不太喜歡,有SQL注入的風險,接收是中文,然後代碼拼接的SQL,且SQL直接使用的,沒有mybatis安全的#{}方式。

改變是:mapper傳遞一個實體和SQL,SQL生成的時候不採用取值的方式,而是拼接爲 insert into table(id,name,value) values(#{table.id},#{table.name},#{table.value});這種形式。

@Service
public class BaseServiceImpl<T,T2> implements BaseService<T,T2> {
    @Autowired
    BaseMapper testTableMapper;
    @Override
    public Long shareInsert(T t) {
        //獲取表名
        String entityName = "";
        if ( null == t.getClass().getAnnotation(Table.class) ){
            //如果沒加table自定義標籤,直接使用實體類類名做爲表名
            entityName = t.getClass().getSimpleName();
        }else{
            //反之使用自定義標籤的名稱作爲表名
            entityName = t.getClass().getAnnotation(Table.class).name();
        }
        //聲明SQL字符串並寫上固定的SQL
        StringBuilder sql = new StringBuilder("insert into "+entityName+" ");

        //獲得SQL
        Map<String,String> sqlMap = getAllPropertiesForSql(t, entityName);

        //拼接爲完整SQL
        sql.append(sqlMap.get("field")).append(sqlMap.get("value"));

        return testTableMapper.shareInsert(sql.toString());
    }

    private Map<String,String> getAllPropertiesForSql(Object t, String objName){
        Map<String,String> map = new HashMap<>();
        if(null == t) return map;
        StringBuilder filedSql = new StringBuilder("(");
        StringBuilder valueSql = new StringBuilder("value (");
        //獲取所有屬性
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 判斷該成員變量上是不是存在Column類型的註解
            if (!field.isAnnotationPresent(Column.class)) {
                continue;
            }
            Column c = field.getAnnotation(Column.class);// 獲取實例
            // 獲取元素值
            String columnName = c.name();
            // 如果未指定列名,默認列名使用成員變量名
            if ("".equals(columnName.trim())) {
                columnName = field.getName();
            }
            filedSql.append(columnName + ",");
            //objName 表名  field.getName()字段名 --這裏的表名因爲多表,而要作爲公共的,所以這裏需要調整下,可以不傳表名而採用泛型
            valueSql.append("#{" + objName + "." + field.getName() + "},");
        }
        //最後一個逗號刪除掉
        valueSql.deleteCharAt(valueSql.length() - 1);
        filedSql.deleteCharAt(filedSql.length() - 1);
        //加上結尾的反括號
        valueSql.append(") ");
        filedSql.append(") ");
        //添加到返回map中
        map.put("field",filedSql.toString());
        map.put("value", valueSql.toString());
        return map;
    }

得到的結果是:

insert into testTable (testId,testName,testValue) value (#{testTable.id},#{testTable.name},#{testTable.testValue}) 

這種格式在xml裏面就安全得多。PS:因測試所以直接寫死,作爲共用的需要採用泛型

記錄到此完結

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章