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

猜你喜欢

转载自blog.csdn.net/u011961421/article/details/79305096