- 基础增删改查接口,往往不应该耗费过多的精力去重复造轮子。这里借助JPA中已有的方法。在上层实现各类业务的功能。
BaseEntity
- 数据库表通用字段。比如每个表都有的主键,更新时间,创建时间,更新人,创建人,记录状态等。
// @Getter,@Setter来自于 Lombok,如果不先还可以换成自生成的getter,setter。
@Getter
@Setter
@MappedSuperclass
@DynamicUpdate
@DynamicInsert
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
protected Long id;
@CreationTimestamp
protected java.sql.Timestamp createTime;
@UpdateTimestamp
protected java.sql.Timestamp updateTime;
private String createBy;
private String updateBy;
private Integer status;
}
Repo
- Repository接口类这里不做封装。
BaseService
- 统一的
service
基类。这里的T
就是Entity
。ID
代表主键类型。R
代表Repository
相关接口。
public interface BaseService<T,ID, R extends JpaRepository<T, ID>> extends JpaRepository<T, ID>{
// empty.
}
BaseServiceImpl
- 实现BaseService的所有方法。其中
save
增加部分更新的功能。其余全部通过直接调用repository实现。
public abstract class BaseServiceImpl<T extends BaseEntity, ID, R extends JpaRepository<T, ID>> implements BaseService<T, ID, R> {
public final R repo;
public BaseServiceImpl(R repo) {
this.repo = repo;
}
@Override
public <S extends T> S save(S s) {
T oldT = null;
// 这里借助反射 & BeanUtils。copyProperties,实现类似mybatis plus updateSelective的效果,只更新非null字段。
if(null != s.getId() && this.repo.existsById((ID)s.getId())) {
Optional<T> t = this.repo.findById((ID)s.getId());
if (t.isPresent()) {
oldT = t.get();
BeanUtils.copyProperties(oldT, s, genNotNullAttrs(s));
}
}
return this.repo.save(s);
}
@Override
public Optional<T> findById(ID id) {
return this.repo.findById(id);
}
@Override
public boolean existsById(ID id) {
return this.repo.existsById(id);
}
@Override
public long count() {
return this.repo.count();
}
@Override
public void deleteById(ID id) {
this.repo.deleteById(id);
}
@Override
public void delete(T t) {
this.repo.delete(t);
}
@Override
public List<T> findAll() {
return this.repo.findAll();
}
@Override
public List<T> findAll(Sort sort) {
return this.repo.findAll(sort);
}
@Override
public List<T> findAllById(Iterable<ID> iterable) {
return this.repo.findAllById(iterable);
}
@Override
public <S extends T> List<S> saveAll(Iterable<S> iterable) {
return this.repo.saveAll(iterable);
}
@Override
public void flush() {
this.repo.flush();
}
@Override
public <S extends T> S saveAndFlush(S s) {
return this.repo.saveAndFlush(s);
}
@Override
public void deleteInBatch(Iterable<T> iterable) {
this.repo.deleteInBatch(iterable);
}
@Override
public void deleteAllInBatch() {
this.repo.deleteAllInBatch();
}
@Override
public T getOne(ID id) {
return this.repo.getOne(id);
}
@Override
public <S extends T> List<S> findAll(Example<S> example) {
return this.repo.findAll(example);
}
@Override
public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
return this.repo.findAll(example, sort);
}
@Override
public Page<T> findAll(Pageable pageable) {
return this.repo.findAll(pageable);
}
@Override
public void deleteAll(Iterable<? extends T> iterable) {
this.repo.deleteAll(iterable);
}
@Override
public void deleteAll() {
this.repo.deleteAll();
}
@Override
public <S extends T> Optional<S> findOne(Example<S> example) {
return this.repo.findOne(example);
}
@Override
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
return this.repo.findAll(example, pageable);
}
@Override
public <S extends T> long count(Example<S> example) {
return this.repo.count(example);
}
@Override
public <S extends T> boolean exists(Example<S> example) {
return this.repo.exists(example);
}
public String[] genNotNullAttrs(Object c) {
List<String> res = new ArrayList<>();
Field[] fields = c.getClass().getDeclaredFields();
for (Field field : fields) {
String name = field.getName();
field.setAccessible(true);
try {
if (null != field.get(c)) {
res.add(name);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
Field[] fields1 = c.getClass().getSuperclass().getFields();
for (Field field : fields1) {
String name = field.getName();
field.setAccessible(true);
try {
if (null != field.get(c)) {
res.add(name);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return res.toArray(new String[0]);
}
}
BaseController
restful接口基类。
-
T
: entity实体类型注入。 -
ID
: entity中主键类型注入。 -
VO
: api接口 更新/插入 参数类. -
DTO
: entity对应的接口返回类. -
R
: repository系列。 -
S
: 业务相关的service.
VO,DTO: VO与DTO可能与entity实际相差不大。也可以说是entity的一种变形,因为某些情况下,数据库中的数据字段值是无法直接提供给前端展示的。又或者前端返回的数据字段无法直接存储到数据库中,必须做相应的转换。一般情况应该包含两种VO(列表查询VO,插入/更新VO), 及两种DTO(列表DTO, 详情返回DTO)。当然也有不用VO,DTO直接使用Entity的,没什么谁好谁坏,具体视情况&业务复杂程度而定。
public abstract class BaseController<T, ID, VO, DTO,R extends JpaRepository<T,ID>, S extends BaseService<T,ID, R>>{
final S service;
public BaseController(S service) {
this.service = service;
}
@PostMapping("save")
public ResponseEntity<DTO> save(@RequestBody VO vo) {
T data = this.service.save(this.persistence(vo));
return new ResponseEntity<>(this.generate(data), HttpStatus.OK);
}
@GetMapping("del/{id}")
public ResponseEntity<String> del(@PathVariable("id") ID id) {
this.service.deleteById(id);
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@GetMapping("list")
public ResponseEntity<List<DTO>> list() {
List<T> data = this.service.findAll();
List<DTO> res = new ArrayList<>();
data.forEach(singleData -> {
res.add(this.generate(singleData));
});
return new ResponseEntity<>(res, HttpStatus.OK);
}
@GetMapping("detail/{id}")
public ResponseEntity<DTO> detail(@PathVariable ID id) throws YourCustomException {
Optional<T> data = this.service.findById(id);
if (! data.isPresent()) {
throw new YourCustomException("the record does not exist!");
}
return new ResponseEntity<>(this.generate(data.get()), HttpStatus.OK);
}
// 将前端的返回的VO转换为数据库存储的entity
public abstract T persistence(VO vo);
// 将数据库返回的entity,转换为DTO
public abstract DTO generate(T t);
// 更新基础字段。例如更新操作,应当更新 ‘更新时间’,‘更新人员’,
// 插入操作应当更新 ‘更新时间’, ‘更新人员’,‘创建时间’, ‘创建人员’
public void updateBaseField(BaseEntity entity) {
if (null == entity.getId()) {
entity.insertInitialize();
} else {
entity.updateInitialize();
}
}
}
- 借助上述基类。实现一个简单的业务功能的CURD
实现User业务模块
UserEntity
@Getter
@Setter
@Entity
@Table(name = "your_user_table_name")
public class UserEntity extends BaseEntity {
private String userName;
// ...省略其他attr.
}
UserRepository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
}
UserSerivce
public interface UserService extends BaseService<UserEntity, Long, UserRepo> {
// 这里可以实现除了crud之外的其他复杂业务逻辑接口
// .....
}
UserServiceImpl
@Service
public class UserServiceImpl extends BaseServiceImpl<UserEntity, Long, UserRepo> implements UserService {
@Autowired
public UserServiceImpl(UserRepo repo) {
super(repo);
}
}
UserVO
public class UserVO{
private String userName;
private Long userId;
}
UserDTO
public class UserDTO {
private String userName;
private String userid;
}
UserController
@RestController
@RequestMapping("/ your_context_path/userInfo")
public class UserController extends BaseController<UserEntity, Long, UserVO, UserDTO, UserRepo, UserService>{
@Autowired
public UserController(UserSerivce service) {
super(service);
}
@Override
public UserEntity persistence(UserVO vo) {
// vo -> entity的赋值,转换
return new UserEntity();
}
@Override
public UserDTO generate(UserEntity data) {
// entity -> DTO的转换赋值。
return new UserDTO();
}
}
- 到此处,整个User模块的CRUD基本接口完成。有兴趣的小伙伴还可以加上其他的公共接口。✌️✌️