SpringBoot學習(七)--封裝Mybatis實現通用對象的增刪改查

版權聲明:作者原創,轉載請註明出處。
本系列文章目錄地址:http://blog.csdn.net/u011961421/article/details/79416510

簡介

Mybatis的動態SQL擁有良好的靈活性和擴展性,但同時這也使得開發過程變得繁瑣,單表的增刪改查最原子操作都需要從Service實現到Sqlmap,一定程度上影響了開發效率,究此原因,博主結合自身經驗通過註解方式簡單了封裝了一層對象單表操作功能。

注意!注意!封裝過程是基於對象中添加自定義註解,然後通過反射解析Class動態生成sql實現,不要和Mybatis API的SqlSession中的方法混淆,並且SpringBoot集成Mybatis後不要輕易使用SqlFactory和SqlSession,原因可參看Mybatis API(http://www.mybatis.org/spring/zh/using-api.html),如下:

  • 它不會參與到 Spring 的事務之中。
  • 如果 SqlSession 使用 DataSource,它也會被 Spring 事務管理器使用,而且當前 有事務在進行時,這段代碼會拋出異常。
  • MyBatis 的 DefaultSqlSession 是線程不安全的。如果在 bean 中注入了它,就會 發生錯誤。
  • 使用 DefaultSqlSession 創建的映射器也不是線程安全的。如果你將它們注入到 bean 中,是會發生錯誤的。
  • 你必須保證在 finally 塊中來關閉 SqlSession。

實戰

1.首先思考最終需求,需要調用者僅傳入單個對象作爲入參即可完成對象的保存,更新,刪除等,不需要再去寫mapper、sql等,那麼即可列出需要實現的接口:

public interface BaseServiceClient {

    public int insert(Object obj);

    public HashMap query(long id, Class c);

    public int update(Object obj);

    public int delete(Object obj);
}

如上,query方法由於Mybatis的resultType必須在xml中指定,而動態改變resultType需要實現更底層接口這邊不做深入,所以返回HashMap。

2.首先以保存方法爲例,入參爲對象,意味着我們需要將對象動態轉化爲sql,自然而然大家肯定會想到通過Java反射機制,那麼再結合Mybatis的動態SQL,我們便可以實現。
首先寫出通用sql:

<insert id="insert" parameterType="java.util.HashMap">
    INSERT INTO ${TABLE_NAME} (
    <foreach collection="COLUMNS" item="item" index="index" separator=",">
        ${item}
    </foreach>
    ) values (
    <foreach collection="VALUES" item="item" index="index" separator=",">
        #{item}
    </foreach>
    )
</insert>

注意${}和#{}的區別,前者會直接將傳入的數據顯示在sql中,而後者#{}格式在mybatis中會使用Preparement語句來安全地設置值,並且傳入的數據會被當做字符串,自動加上雙引號。

3.通過定義好的sql,可以看出我們需要的HashMap格式爲:{TABLE_NAME=表名, COLUMNS=[列名], VALUES=[列值]},下面需要做的就是封裝好數據,這裏我們加入自定義註解方便取值。
自定義註解如下(篇幅有限,對於自定義註解這邊不做介紹,可以自行百度):

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
    String value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    String value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Id {
    String value();
}

4.使用註解編寫對象類@Table爲表名,@Id爲主鍵,@Column爲列名,注意@Id和@Column需要定義在get方法上。

@Table(value = "CMS_USER_INFO")
public class UserInfo extends BaseEntity {
    // 主鍵
    private long id;
    // 用戶編號
    private String userCode;
    // 用戶名稱
    private String userName;
    // 用戶密碼
    private String userPwd;
    // 備註
    private String remark;

    @Id(value = "ID")
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Column(value = "USER_CODE")
    public String getUserCode() {
        return userCode;
    }

    public void setUserCode(String userCode) {
        this.userCode = userCode;
    }

    @Column(value = "USER_NAME")
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Column(value = "USER_PWD")
    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    @Column(value = "REMARK")
    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    @Override
    public String toString() {
        return "UserInfo{" +
                "id=" + id +
                ", userCode='" + userCode + '\'' +
                ", userName='" + userName + '\'' +
                ", userPwd='" + userPwd + '\'' +
                ", remark='" + remark + '\'' +
                super.toString() +
                '}';
    }
}

5.到此,只差最後一步,需要我們編寫基礎方法通過反射解析便可,即最開始定義的BaseServiceClient接口的實現,代碼如下:

@Service(value = "baseServie")
public class BaseServiceClientImpl implements BaseServiceClient {
    private static final Logger log = LoggerFactory.getLogger(BaseServiceClientImpl.class);

    @Autowired
    private BaseMapper baseMapper;

    private Map<String, Object> transformObj(Object t) {
        //獲取表名
        if (null == t.getClass().getAnnotation(Table.class)) {
            throw new RuntimeException("Error Input Object! Error @Table Annotation.");
        }
        Map<String, Object> re = new HashMap<String, Object>();
        re.put("TABLE_NAME", t.getClass().getAnnotation(Table.class).value());

        Method[] m = t.getClass().getMethods();
        if (null == m || m.length <= 0) {
            throw new RuntimeException("Error Input Object! No Method.");
        }

        List k = new ArrayList();//存放列名
        List v = new ArrayList();//存放列值
        for (Method i : m) {
            //獲取列名和值
            try {
                if (null != i.getAnnotation(Column.class)) {
                    k.add(i.getAnnotation(Column.class).value());
                    v.add(i.invoke(t, null));
                    continue;
                }
                //獲取主鍵
                if (null != i.getAnnotation(Id.class)) {
                    re.put("KEY_ID", i.getAnnotation(Id.class).value());
                    re.put("KEY_VALUE", i.invoke(t, null));
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Error Input Object! Error Invoke Get Method.", e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Error Input Object! Error Invoke Get Method.", e);
            }
        }
        re.put("COLUMNS", k);
        re.put("VALUES", v);
        if (k.size() != v.size()) {
            throw new RuntimeException("Error Input Object! Internal Error.");
        }
        return re;
    }

    @Override
    public int insert(Object obj) {
        Map<String, Object> params = transformObj(obj);
        log.info(new StringBuffer("Function Insert.Transformed Params:").append(params).toString());
        return baseMapper.insert(params);
    }
}

6.最終我們的基礎服務是提供給其他服務調用的,下面編寫測試方法,來測試一下,如下:

public interface UserService {

    /**
     * 新增用戶信息
     * @param userInfo
     */
    public void addUserInfo(UserInfo userInfo);
}
@Service(value = "userService")
public class UserServiceImpl implements UserService {
    @Autowired
    private BaseServiceClient baseServiceClient;

    @Override
    public void addUserInfo(UserInfo userInfo) {
        baseServiceClient.insert(userInfo);
    }
}

測試類:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceImplTest {
    @Autowired
    private UserService userService;

    @Test
    public void addUserInfos() throws Exception {
        UserInfo u1 = new UserInfo();
        u1.setUserCode("112233");
        u1.setUserName("測試名字");
        u1.setUserPwd("111000");
        u1.setCreateBy("mko1");
        u1.setCreateDate(new Date());
        u1.setSortNo(99);
        u1.setState(1);
        userService.addUserInfo(u1);
    }
}

運行測試方法,查看後臺日誌:
這裏寫圖片描述
可以看到最終Mybatis調用sqlmap傳入的HashMap爲:{TABLE_NAME=CMS_USER_INFO, KEY_ID=ID, COLUMNS=[USER_NAME, USER_PWD, USER_CODE, REMARK, STATE, CREATE_DATE, CREATE_BY, MODIFIED_DATE, MODIFIED_BY, SORTNO], VALUES=[測試名字, 111000, 112233, null, 1, Sat Feb 10 15:16:53 CST 2018, mko1, null, null, 99], KEY_VALUE=0}

執行SQL爲:

  • Preparing: INSERT INTO CMS_USER_INFO ( USER_NAME , USER_PWD , USER_CODE , REMARK , STATE , CREATE_DATE , CREATE_BY , MODIFIED_DATE , MODIFIED_BY , SORTNO ) values ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )
  • Parameters: 測試名字(String), 111000(String), 112233(String), null, 1(Integer), 2018-02-10 15:16:53.217(Timestamp), mko1(String), null, null, 99(Integer)

到此,我們已經實現了public int insert(Object obj) 方法,那麼對於刪除和查詢,顯然比這個簡單的多,只需要取到主鍵ID便可操作,transformObj方法可以複用。對於update的方法,其實理解了insert的思想,實現上幾乎是一樣的,只是定義的數據格式有些差別,大家可以思考一下自行實現,或參考下文GitHub。

寫在最後,由於時間和能力有限,博主還未對次方式做進一步的改進和優化,肯定存在多多少少的細節問題,望大家不吝賜教,同時本人歸納總結的時也會同步更新。
本SpringBoot學習系列及本章GitHub地址爲:https://github.com/15651037763/cms

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