本篇的内容,真是颠覆了我对Mybatis所能干的事情的又一新的认识,唯有不断的学习,才能发现自身的不足,唯有发现自身的不足,才足以使得我们写的每一段代码都闪闪发光!
一、相比传统的Web项目,如果数据层业务不是很复杂的情况下,我们采用以下模式:
1.用户类【JavaBean -->要操作的数据或对象】
/**
* 简单用户实体类:JavaBean
* 继承自抽象类Person,得到父类的年龄和性别属性
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2017年12月1日-上午8:50:05
*/
public class User extends Person {
private int uID; // 用户ID
private String uName; // 用户名
private String uPwd; // 用户密码
private int uRole; // 用户角色 1:普通用户 2:超级管理员
public User(int ID, String Name, String Pwd, int Role) {
this.uID = ID ;
this.uName = Name;
this.uPwd = Pwd ;
this.uRole = Role;
}
public User(){
}
public int getuID() {
return uID;
}
public void setuID(int uID) {
this.uID = uID;
}
public String getuName() {
return uName;
}
public void setuName(String uName) {
this.uName = uName;
}
public String getuPwd() {
return uPwd;
}
public void setuPwd(String uPwd) {
this.uPwd = uPwd;
}
public int getuRole() {
return uRole;
}
public void setuRole(int uRole) {
this.uRole = uRole;
}
//输出【out】对象时,调用对象的toString,打印相关信息
public String toString(){
return "ID:"+uID+",Name:"+uName+",Role:"+uRole;
}
}
2.创建用户接口【接口里面定义,增删改查等行为】
/**
* 用户操作接口
* @author [email protected]
* @date 2017年12月1日-下午4:32:34
*/
public interface UserService {
/**
* 创建一个新用户
* @param user
* @return
*/
public boolean CreateUser(User user) throws Exception;
}
3.创建用户Mapper接口
public interface UserMapper {
int insert(
@Param("user") User user) throws Exception;
}
4.配置用户Mapper接口相关的sql语句,对应XXXMapper.xml
user
id,
name,
age,
sex,
pwd,
role
INSERT INTO
(
name,
age,
sex,
pwd,
role
) VALUES (
#{user.uName},
#{user.age},
#{user.sex},
#{user.uPwd},
#{user.uRole}
)
5.用户接口的新增用户的业务实现
@Service
@Primary
public class UserServiceImpl implements UserService {
//注入mapper
@Autowired
private UserMapper uMapper;
@Override
public boolean CreateUser(User user) throws Exception{
if(uMapper.insert(user)>0){
return true;
}
return false;
}
}
6.用户操作对应的Controller测试
@RestController // same as @Controller + @ResponseBody
@RequestMapping("/rest/v1.0.1/database/user") // restful风格的api接口
public class UserController {
@Autowired
private UserService uService;
/**
* 1.利用传统Service层进行用户新建
*
* @param user
* @return
* @throws Exception
*/
@PostMapping("/create")
public String CreateUser(@RequestBody User user) throws Exception {
if (uService.CreateUser(user)) {
return user.getuName() + ",用户创建成功";
} else {
return user.getuName() + "用户创建失败";
}
}
}
7.API工具测试
(1)
(2)
(3)
(4)总结
如果项目中,业务涉及的操作多达几百个,也就是说,假设一个Service接口,基础的业务操作行为有4个,增删改查,如果这里我们所说的业务操作有200个的话,也就是说,我们要写50个Service,本篇例子,我们也只是单单写了一个用户Service接口
这样一来,我们就要写50个对应的业务Mapper接口和50个业务Mapper接口对应的SQL配置XML【甚至可能会更多】
本篇,我们也是单单写了一组用户Mapper
可想而知,随着项目中业务数量的增多,传统的Service层压力会很大,至少,里面一堆业务操作,明着说,就是一堆代码,就算写的代码很规范,看上去也会让人很头疼,代码量超过200行,可读性就已经大大折扣了,代码可不是给用户看的,而是给我们后端或者说是代码的开发者和维护者看的,如果一个业务操作行为出了bug,你是不是要在其对应的Service里面找到,这个业务操作的实现部分,如果代码量很多,你会花一些时间查找,如果找到了,是SQL语句的问题,你是不是又要找对应的Mapper接口,根据Mapper接口再找到对应的Mapper.xml里面的sql语句,总之,你不仅要在service层中把属于同一个数据实体的不同操作行为合起来写成一个service,你还要在XXXMapper.xml中配置很多很多的SQL语句,听起来是不是很繁琐.
service中集中写业务的实现这个倒不会让我们开发人员和维护人员感到恐惧的地方,而配置过多的sql语句,真的会让人奔溃,而且很多sql都比较简单,针对单表的操作,显得比较low,又比较费时,我们虽然想到了Mybatis的逆向工程可以自动根据数据库表和Java类进行关系映射,产生对应的XXXMapper.xml,省去我们手动配置sql语句的麻烦,但是,这种逆向工程产生的Mapper很庞大,其中好多方法和sql语句都不是我们所需要的。
我们做项目,在实现业务功能的同时,我们还要考虑,项目的维护成本【扩展性、可读性、BUG易发现、BUG易排查...】和项目的大小,前期我们主要实现业务,怎么简单怎么来,后期我们主要对项目进行优化,所谓优化,不是盲目的,而是带有目的性的,目的很简单,就是如何让我们的项目变得更加健壮!
针对上述所阐述的问题,我们采用MyBaits的另一种模式才来构建我们的数据业务层 ---利用通用Mapper
【通用Mapper】 : 我简单理解是,复杂度介于逆向工程和手动配置SQL语句之间,能满足开发者的基本需求
说明:上述只是个人理解,这种模式针对单表操作,非常暴力!如果是多表联合查询等复杂操作,仍需要手动配置SQL,除此之外,我们在项目中不用写一句sql,对,sql语句不用写!
二、POM.xml 添加相关Mapper依赖
tk.mybatis
mapper-spring-boot-starter
1.1.5
三、创建所有业务操作的父类【抽象、泛型】
DatabaseAction.java
package com.appleyk.actions;
/**
* 描述:所有业务【行为】的父类【泛型】
* 传统的Service层,里面可能包含增删改查【基础】或是其他的业务行为
* 比如,一个service中,至少有4个业务行为,我们用英文单词"Action"表示
* 如果,不细分service,当项目非常庞大、业务非常多的时候,我们的Service层会看起来一团糟
* 而且,每一个service会对应好多个xxxMapper.xml
* 每个xxxMapper.xml里面要重复的配置和写上很多类似的sql语句,随着项目的业务扩展,维护成本非常大,很耗时
* 因此,我们使用Mybatis的通用mapper来对复杂项目进行组织架构【包括良好的扩展性和细化的行为维护】
* 这种组织思想,就是不管3*7等于21,我就是要把数据库中所有表的结构一一映射成一个个对应的Java实体类
* 然后,这个实体类的具体sql层的mapper操作,我们交给Mybatis来处理【里面有最基础的增删改查方法,避免我们再去手动配置sql】
* 当然,这个实体类对应的不止一个业务Action,没关系,业务的具体实现我们通过实现父类的execute方法来具体问题具体分析
* 针对这种"架构"【对当前项目来说,这种思想就是一种架构,一种可以让复杂项目起死回生的不错的选择】
* 为每一个操作创建一个XXXAction,比如,添加一个用户,我们可以对应CreateUserAction,并继承父类DatabaseAction
* 为每一个操作创建一个XXXAction,比如,删除一个用户,我们可以对应DeleteUserAction,并继承父类DatabaseAction
* 这样一来,XXXAction就可以实现父类的execute方法
* 这个方法就是单个Action的业务实现部分,具体怎么实现,是否还可以细分,暂不考虑,因为,我们已经从Sercice层抽离出来了每个操做行为
*
*
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2017年12月11日-上午10:47:29
* @param -- 对应不同的业务行为要操作的数据
*/
public abstract class DatabaseAction {
/**
* 业务行为 要 操作的 数据 【对象】
*/
private T data;
public DatabaseAction(T data) {
this.data = data;
}
/**
* 执行操作动作
*
* @return
*/
public abstract boolean execute() throws Exception;
/**
* 当前动作对应的操作标志
*
* @return
*/
public abstract Integer getOperations();
/**
* 父类里面直接实现getData方法,子类直接调用父类的改方法即可,也可以根据情况自行改写改方法
* @return 数据 或 对象
*/
public T getData() {
return data;
}
}
四、创建User的实体类和对应的Mapper接口
(1)
(2)UserEntity.java
package com.appleyk.data.entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.ibatis.type.JdbcType;
import com.appleyk.config.SpringContextUtil;
import com.appleyk.data.mapper.UserEntityMapper;
import com.appleyk.pojo.User;
import tk.mybatis.mapper.annotation.ColumnType;
/**
* 数据库单表对应的Java实体类【什么都不用考虑,直接对着表字段从上下捋下来】
*
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2017年12月11日-上午10:22:29
*
*/
@Table(name = "user") //当前实体类标识的表名称:可以对应数据库中的真实表名,也可以自定义
public class UserEntity {
private final static UserEntityMapper uEntityMapper = SpringContextUtil.getApplicationContext().getBean(UserEntityMapper.class);;
/**
* 1. @Id注解标识主键以及@ColumnType注解标识主键字段类型
* 可以有多个@Id注解,标识联合主键
*/
@Id
@ColumnType(jdbcType = JdbcType.INTEGER)
private int id;
/**
* 2.用户名
*/
private String name;
/**
* 3.年龄
*/
private int age;
/**
* 4.性别
*/
private String sex;
/**
* 5.用户密码
*/
private String pwd;
/**
* 6.用户角色 --> 1:超级管理员 2:普通用户
*/
private int role;
/**
* A.公有 -- 无参构造器【作为保留,一定要有】
*/
public UserEntity(){
}
/**
* B.私有 -- 有参构造器,初始化字段【包括字段过滤】
*/
private UserEntity(User user){
this.id = user.getuID(); //由于id在MySql中是自动增长列,所以这里id不处理
//1.name字段过滤,如果前端传过来的name值等于空,我们就给来个默认的"无名氏",总得找点乐子,哈哈!
this.name = user.getuName() == "" ? "无名氏" :user.getuName();
//2.这里我们只演示一个字段条件过滤【本应该Controller中进行条件过滤的,我们却放在了实体类中进行,想一想有什么好处?】
this.age = user.getAge();
this.sex = user.getSex();
this.pwd = user.getuPwd();
this.role= user.getuRole();
}
//返回UserEntity实体对应的MapperBean
public static UserEntityMapper getBean(){
//从Spring上下文里面获取对应的Bean
return uEntityMapper;
}
/**
* C. 根据对应的JavaBean,获得当前实体类的实例
* 这个私有构造器,放在实体类的内部进行调用 【等同于对象new的时候添加了一层保护膜】
* @param user
* @return
*/
public static UserEntity getEntity(User user){
return new UserEntity(user);
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
}
(3)UserEntityMapper.java
package com.appleyk.data.mapper;
import com.appleyk.data.entity.UserEntity;
import tk.mybatis.mapper.common.Mapper;
/**
* 这里,我们什么都不需要做,我们交给MyBatis的Mapper进行处理
* 一个entity 对应一个 XXXMapper
* @author [email protected]
* @blob http://blog.csdn.net/appleyk
* @date 2017年12月11日-上午11:12:51
*/
public interface UserEntityMapper extends Mapper {
}
(4)有个地方需要注意
(5)Mybatis全局配置文件【手动配置DataSource已经包扫描】
A.
B.
MyBatisConfig.java
package com.appleyk.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import com.github.pagehelper.PageInterceptor;
@Configuration
//使用注解 @EnableTransactionManagement 开启事务支持后
//然后在访问数据库的Service方法上添加注解 @Transactional便可。
@EnableTransactionManagement
@EnableConfigurationProperties(DataSourceProperties.class)
//扫描一切和Mapper有关的bean,因此,下面对整个项目进行"全身"扫描
@MapperScan("com.appleyk")
public class MyBatisConfig implements TransactionManagementConfigurer{
@Bean(name = "dataSource")
//Spring 允许我们通过 @Qualifier注释指定注入 Bean 的名称
@Qualifier(value = "dataSource")
@ConfigurationProperties(prefix="jdbc")
@Primary
public DataSource dataSource()
{
return DataSourceBuilder.create().build();
}
//创建SqlSessionFactory
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dataSource") DataSource dataSource){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
//1.设置数据源
bean.setDataSource(dataSource);
//2.给包中的类注册别名,注册后可以直接使用类名,而不用使用全限定的类名(就是不用包含包名)
bean.setTypeAliasesPackage("com.appleyk.database");
// 设置MyBatis分页插件 【PageHelper 5.0.1设置方法】
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("offsetAsPageNum", "true");
properties.setProperty("rowBoundsWithCount", "true");
pageInterceptor.setProperties(properties);
//添加插件
bean.setPlugins(new Interceptor[]{pageInterceptor});
//添加XML目录,进行Mapper.xml扫描
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
//项目中的xxxMapper.xml位于包com.appleyk.database下面
bean.setMapperLocations(resolver.getResources("classpath*:com/appleyk/database/*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//创建SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
(6)Spring上下文工具类
SpringContextUtil.java
package com.appleyk.config;
import org.springframework.context.ApplicationContext;
public class SpringContextUtil {
private static ApplicationContext applicationContext;
// 获取上下文
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
// 设置上下文
public static void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
// 通过名字获取上下文中的bean
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
// 通过类型获取上下文中的bean
public static Object getBean(Class> requiredType) {
return applicationContext.getBean(requiredType);
}
}
(7)我们在哪里获得当前项目的Spring上下文对象呢?
五、创建User的新建和删除对应的Action
(1)
(2)
CreateUserAction.java
package com.appleyk.actions;
import com.appleyk.data.entity.UserEntity;
import com.appleyk.data.mapper.UserEntityMapper;
import com.appleyk.pojo.User;
public class CreateUserAction extends DatabaseAction{
/**
* 2 : ADDING
* 32: USER
*/
public final static Integer operations = 2 | 32;
UserEntityMapper uEntityMapper;
public CreateUserAction(User data) {
super(data);
//拿到Bean
uEntityMapper = UserEntity.getBean();
}
@Override
public boolean execute() throws Exception {
//1.先创建一个实体类
UserEntity userEntity = UserEntity.getEntity(this.getData());
//2.然后,由Mybatis的Mapper执行对应的操作
//3.具体点,就是交由User用户这个实体对应的Mapper接口执行相应的操作
//4.然后这个操作,就是当前这个CreateUserAction行为的具体业务实现
int nRet = uEntityMapper.insert(userEntity);
//5.如果插入成功,返回true
if(nRet > 0){
return true;
}
//6.如果插入失败,返回false
return false;
}
@Override
public Integer getOperations() {
return operations;
}
public static void main(String[] args){
System.out.println(2|32);
}
}
(3)
DeleteUserAction.java
package com.appleyk.actions;
import com.appleyk.data.entity.UserEntity;
import com.appleyk.data.mapper.UserEntityMapper;
import com.appleyk.pojo.User;
public class DeleteUserAction extends DatabaseAction {
/**
* 3 : DELETEING 32: USER
*/
UserEntityMapper uEntityMapper;
public final static Integer operations = 3 | 32;
public DeleteUserAction(User data) {
super(data);
//拿到Bean
uEntityMapper = UserEntity.getBean();
}
@Override
public boolean execute() throws Exception {
//1.根据主键ID 进行record删除
int nRet = uEntityMapper.deleteByPrimaryKey(this.getData().getuID());
if (nRet > 0) {
return true;
}
return false;
}
@Override
public Integer getOperations() {
return operations;
}
}
(1)
UserController.java
package com.appleyk.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.appleyk.actions.CreateUserAction;
import com.appleyk.actions.DeleteUserAction;
import com.appleyk.pojo.User;
import com.appleyk.result.ResponseMessage;
import com.appleyk.result.ResponseResult;
import com.appleyk.service.UserService;
@RestController //same as @Controller + @ResponseBody
@RequestMapping("/rest/v1.0.1/database/user") //restful风格的api接口
public class UserController {
@Autowired
private UserService uService;
/**
* 1.利用传统Service层进行用户新建
* @param user
* @return
* @throws Exception
*/
@PostMapping("/create")
public String CreateUser(
@RequestBody User user) throws Exception{
if(uService.CreateUser(user)){
return user.getuName()+",用户创建成功";
}
else{
return user.getuName()+"用户创建失败";
}
}
/**
* 2.利用通用mapper进行用户新建
* @param user
* @return
* @throws Exception
*/
@PostMapping("/mapper/create")
public ResponseResult CreateUserByAction(
@RequestBody User user) throws Exception{
CreateUserAction action = new CreateUserAction(user);
if(action.execute()){
return new ResponseResult(ResponseMessage.OK);
}
return new ResponseResult(ResponseMessage.UN_ACCEPT);
}
/**
* 3.利用通用mapper进行用户删除
* @param user
* @return
* @throws Exception
*/
@PostMapping("/mapper/delete")
public ResponseResult DeleteUserByAction(
@RequestBody User user) throws Exception{
DeleteUserAction action = new DeleteUserAction(user);
if(action.execute()){
return new ResponseResult(ResponseMessage.OK);
}
return new ResponseResult(ResponseMessage.UN_ACCEPT);
}
}
(2)测试前,user表数据
(3)利用通用Mapper进行新增用户的行为Action测试
A.
B.
(4)利用通用Mapper进行删除用户的行为Action测试
A.
B.
C.我们在项目中一句sql语句都没有写,就完成了用户记录的新增和删除操作,我们是怎么实现的呢?最后,我再带大家捋一遍
1.创建用户表对应的实体类【UserEntity】【区别于业务层要操作的数据--User实体类】
2.每一个实体类XXXEntity对应一个XXXEntityMapper接口
3.每一个XXXEntityMapper接口都继承Mapper<对应的实体类>
4.Mapper中,Mybatis帮助我们实现了简单的单表增删改查操作
5.创建所有业务操作的基类DatabaseAction
6.为每一个操作行为创建一个对应的Action
7.通过Spring上下文获得对应XXXEntityMapper的Bean
8.每一个Action继承自DatabaseAction,通过重写excute方法,执行业务的操作
9.每一个Action的业务操作实现,都需要用到XXXEntityMapper的代理实例,实例通过getBean(XXX.class)获得
10.Controller决定哪个Action被执行
好处:
1.通过Aciton的名称,我们就可以知道其内部的业务具体是干什么操作的,不像给你个Service,你都不一定能猜全这个Service里面具体涉及到的业务有哪些。
2.每一个Action的业务代码不会太臃肿,单表操作的话,有效代码不会操作50行【复杂业务实现除外】
3.项目代码易维护、易读,BUG很容易定位到,是哪个Action出了问题。
4.条例清晰,书写代码心情愉悦,就像街边的垃圾分类存放一样,对于垃圾的可回收利用帮助很大。