目录
一、业务场景demo
数据库
三个数据库,分别是employee、animal、shop
SpringBoot持久层代码
ShopMapper
EmployeeMapper
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:46
* @description ...
*/
@Repository
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE e_id = #{id}")
Employee getEmployeeById(Integer id);
@Select("SELECT * FROM employee")
List<Employee> getAllEmployee();
}
AnimalMapper
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:46
* @description ...
*/
@Repository
public interface AnimalMapper {
@Select("SELECT * FROM animal WHERE a_id = #{id}")
Animal getAnimalById(Integer id);
@Select("SELECT * FROM animal")
List<Animal> getAllAnimal();
}
SpringBoot业务层代码
ShopServiceImpl
/**
* @author LiChao
* @version v1.0.0.20240523_base
* @date 2024/05/23 9:57
* @description ...
*/
@SuppressWarnings({"all"})
@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private ShopMapper shopMapper;
@Override
public Result<Shop> getShopById(Integer id) {
Shop shopById = shopMapper.getShopById(id);
if (shopById != null) {
return Result.success(shopById);
}
return Result.fail("查询的店铺不存在");
}
}
EmployeeServiceImpl
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:53
* @description ...
*/
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Resource
private EmployeeMapper employeeMapper;
@Override
public Result<Employee> getEmployeeById(Integer id) {
Employee employeeById = employeeMapper.getEmployeeById(id);
if (employeeById != null) {
return Result.success(employeeById);
}
return Result.fail("查询的员工不存在");
}
@Override
public Result<List<Employee>> getAllEmployee() {
return Result.success(employeeMapper.getAllEmployee());
}
}
AnimalServiceImpl
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:52
* @description ...
*/
@Service
public class AnimalServiceImpl implements AnimalService {
@Resource
private AnimalMapper animalMapper;
@Override
public Result<Animal> getAnimalById(Integer id) {
Animal animalById = animalMapper.getAnimalById(id);
if (animalById != null) {
return Result.success(animalById);
}
return Result.fail("查询的动物不存在");
}
@Override
public Result<List<Animal>> getAllAnimal() {
return Result.success(animalMapper.getAllAnimal());
}
}
SpringBoot后端控制器代码
这里为了测试方便,就将所有业务都写在一个控制器里了
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:33
* @description ...
*/
@RestController
@RequestMapping("/api")
public class SingleParameterController {
@Resource
private EmployeeService employeeService;
@Resource
private AnimalService animalService;
@Resource
private ShopService shopService;
@GetMapping("animal/{id}")
public Result<Animal> getAnimalById(@PathVariable("id") int id) {
return animalService.getAnimalById(id);
}
@GetMapping("employee/{id}")
public Result<Employee> getEmployeeById(@PathVariable("id") int id) {
return employeeService.getEmployeeById(id);
}
@GetMapping("shop/{id}")
public Result<Shop> getShopById(@PathVariable("id") int id) {
return shopService.getShopById(id);
}
}
通用结果封装类Result
/**
* @author LiChao
* @version v1.0.0.20240325_base
* @date 2024/03/25 20:06
* @description ...
*/
@Data
public class Result<T> implements Serializable {
private static final int SUCCESS_CODE = 1;
private static final int ERROR_CODE = 0;
/**
* 状态码:1为成功,0为失败
*/
private Integer code;
/**
* 错误信息
*/
private String msg;
/**
* 成功数据
*/
private T data;
/**
* 成功回调
*
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(SUCCESS_CODE);
result.setData(data);
return result;
}
public static <T> Result<T> success() {
Result<T> result = new Result<>();
result.setCode(SUCCESS_CODE);
result.setData(null);
return null;
}
/**
* 失败回调
*
* @param msg
* @return
*/
public static <T> Result<T> fail(String msg) {
Result<T> result = new Result<>();
result.setCode(ERROR_CODE);
result.setMsg(msg);
return result;
}
}
这里的Result类就是我自己封装的一个返回通用结果的类,返回了和前端协调好的响应码,响应数据,错误信息等
前端Vue代码
这里就简单的使用ElementUI的Notification通知组件和Button组件+Axios简单的模拟用户请求,这里为了防止后端接口暴露并且希望统一往后端发起请求,所以使用了自己封装了一个http.js工具类向指定的后端URL发请求
<template>
<div class="sigle-param">
<el-row>
<el-button type="primary" plain @click="getEmployeeById(938742)">根据id查询员工</el-button>
</el-row>
<br /><br />
<el-row>
<el-button type="success" plain @click="getAnimalById(33)">根据id查询动物</el-button>
</el-row>
<br /><br />
<el-row>
<el-button type="warning" plain @click="getShopById(2)">根据id查询商品</el-button>
</el-row>
<br /><br />
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'SingleParameter',
methods: {
getEmployeeById(id) {
axios.get("/api/employee/" + id)
.then(response => {
const result = response.data
if (result.code === 1) {
this.$notify({
title: '提示',
message: '获取到的员工姓名为:' + result.data.ename + ', 年龄为:' + result.data.eage,
duration: 0
});
} else {
this.$message.warning(result.msg)
}
}).catch(error => {
this.$message.error("错误请求" + error)
})
},
getAnimalById(id) {
axios.get("/api/animal/" + id)
.then(response => {
const result = response.data
if (result.code === 1) {
this.$notify({
title: '提示',
message: '这个动物是:' + result.data.atype + ', 它的行为是:' + result.data.aaction,
duration: 0
});
} else {
this.$message.warning(result.msg)
}
}).catch(error => {
this.$message.error("错误请求" + error)
})
},
getShopById(id) {
axios.get("/api/shop/" + id)
.then(response => {
const result = response.data
if (result.code === 1) {
this.$notify({
title: '提示',
message: '获取到的商品编号为:' + result.data.sid + ', 价格为:' + result.data.price,
duration: 0
});
} else {
this.$message.warning(result.msg)
}
}).catch(error => {
this.$message.error("错误请求" + error)
})
},
},
}
</script>
<style scoped>
.sigle-param {
margin: auto;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
</style>
最终效果:
点击按钮请求不同的数据
二、代码分析
1、业务层代码冗余
如果我们每次都根据id(或者说主键)做什么什么操作时候,都需要非空判断,甚至业务复杂的时候,需要更多的判断,什么是否为会员,是否过期,名字是否合法等等,对于不同的接口,业务逻辑也不一样,所以有时候我们会在每个接口都写一遍,这就很冗余了。
2、解决方式
我们可以定义一个通用的类,来将比如说传入一个参数的方法对数据库的查询封装起来,下次需要判断,直接调用该类的相应方法就可以,那么,我们怎么知道这个类是什么呢?参数是什么类型呢?以及这段查询数据库的方法又是什么呢?参数类型好说,使用泛型编程就可以完美的解决,类的类型可以使用反射机制拿到Class对象来推断出方法返回值的泛型,那这段查询数据库的逻辑怎么封装呢?我们知道,在Java中,有一种接口叫函数式接口,是不是就可以很好的解决我们的问题呢?那么在Java中需要传入一个参数,并且有返回值的函数式接口是谁呢?就是我们的Function<T, R>,它有一个apply方法刚好符合我们的需要,所以接下来我们创建一个类,并且把这个方法创建出来的结果就会变成这样:
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 22:58
* @description ...
*/
@Component
public class CommonCheckClient {
public <R, P> Result<R> singleParam(P param, Class<R> type, Function<P,R> fun, String msg) {
R r = fun.apply(param);
// TODO: 合法性验证......
// TODO: 是否逻辑过期验证......
// TODO: 开启异步线程资源调度......
// TODO: 获取分布式锁......
// TODO: 微服务网关是否成功路由......
// TODO: 甚至还有会员机制,业务需求是某接口的访问要判断该用户是否为会员,一般我们会
// 写在拦截器或者微服务网关Gateway中,但是出于某些特殊场景,不适合写在全局的服务
// 里面,也可以使用这样的方法抽象出来使代码复用
//
// TODO: 等等等......
/**
* 这里就以一个简单的非空判断作为案例说明
* (其实这里直接 return r就可以,因为 r 为 null 就是 null,有值自然就返回了,这里只是举个例子)
*/
if (r != null) {
return Result.success(r);
}
// 都没有成立,返回错误信息
return Result.fail(msg);
}
}
将它注入到IOC容器中供我们依赖注入使用
3、代码优化
这里就以AnimalService为例,我们可以将查询数据库的方法单独抽出来作为一个独立的方法,返回的就是数据库查出来的实体类对象,然后将该实体类放到通用方法中进行一系列的校验,然后返回给控制器
修改后的代码为:
/**
* @author LiChao
* @version v1.0.0.20240522_base
* @date 2024/05/22 21:52
* @description ...
*/
@Service
public class AnimalServiceImpl implements AnimalService {
@Resource
private AnimalMapper animalMapper;
@Resource
private CommonCheckClient commonCheckClient;
@Override
public Result<Animal> getAnimalById(Integer id) {
return commonCheckClient.singleParam(id, Animal.class, this::getById, "查询的动物不存在");
}
public Animal getById(Integer id) {
return animalMapper.getAnimalById(id);
}
@Override
public Result<List<Animal>> getAllAnimal() {
return Result.success(animalMapper.getAllAnimal());
}
}
这里传入参数id,再传入返回值的类型Class对象,然后将查询数据库的逻辑使用方法引用传给函数式接口,传入如果失败后的错误信息,最终返回给前端。然后就是因为这个demo中的业务实在太简单了,所以我们感觉代码量变多了,还不如原来的,可是如果判断多了,这个是不是就非常的妙了呢?
同样其他的类的代码也变得非常优雅:
三、测试
这里我临时将id改成了不存在的id,前端测试成功!