这时候数据库还没有准备好,接口需求也没有定下来,我们可以做一些早期的封装。早期封装的好,尽量实现低耦合,就和实现快速开发,而且还能应对各种不确定的变化。
一般的接口需求,以获取数据为主。获取数据有些是单一数据类型,有的却是多种数据多种结构组合在一起。比如Android的页面如果比较复杂,就需要组装一套复杂的数据提供。这就导致java后端纵向分割无法确定。
我的观点是,controller是数据提供层,分割的依据是前端提供的模块,依照前端的一级模块或者接口需求的分法确定命名空间。service和dao这两层则是数据处理层,他们是一致的,可以按照数据类型进行划分,也就是基本依照数据表。数据库表中联系紧密的几张表可以算作同一种数据类型。
因为接口需求文档还没有生成,我们不知道controller怎么处理,所以我们只好先处理单一类型的数据。操作单一类型的数据相当于操作一张表,一般有以下几种:
1.获取一条记录
2.获取所有的记录(列表)
3.获取指定分页的记录(封装在分页模型中)
4.删除一条记录(依据id)
5.删除多条数据(依据id集合)
6.获取记录总数
以上六条是确定的。
以下操作是不确定的。
增加一条记录:不确定传进来的参数,除非是用body+json传进来整个的对象,这样需要固定请求模式,但是也没有这样传的,浪费流量;
增加多条记录:同上;
修改一条记录:同上。
从增删改查角度分析,可以确定封装的基本操作有以上六种。我们就按照这六种来进行封装。
一、首先,自定义一个Repository,实现DAO层的封装。实际上,JpaRepository已经把这六种操作封装好了。只是作为程序员,要想使自己的程序够灵活,尽量不要直接用原生的,否则遇到需要修改的时候手忙脚乱。哪怕我们自定义类之后其实什么都没有做,只是路过也没有关系,我们拿到操作权,可以很方便进行维护。
在这里,我给自己加一个需求。JpaRepository中的分页操作的数据类型不合我的使用,我要把数据放在自己定义的PageModel中包起来,我决定在DAO层去实现。
1. 首先自定义一个接口,继承JpaRepository
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
PageModel<T> getPage(Pageable pageable);//获取自定义分页
}
2. 给上面的接口创建一个实现类,继承SimpleJpaRepository,实现类的类名就在接口名后面加上“Impl”就可以了。
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements BaseRepository<T, ID> {
private EntityManager entityManager;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
/**
* 获取自定义分页
*
* @param pageable
* @return
*/
@Override
public PageModel<T> getPage(Pageable pageable) {
PageModel<T> pageModel = new PageModel<T>(pageable.getPageNumber() + 1, pageable.getPageSize());
pageModel.count = count();
pageModel.hasNext = pageModel.page * pageModel.pageSize < pageModel.count;//是否有下一页
pageModel.dataList = findAll(pageable).getContent();
return pageModel;
}
}
在这个实现类中,我们实现了我们自己添加的方法。
3. 创建处理类,继承JpaRepositoryFactoryBean
public class BaseRepositoryFactoryBean<JR extends JpaRepository<T, ID>, T, ID extends Serializable>
extends JpaRepositoryFactoryBean<JR, T, ID> {
public BaseRepositoryFactoryBean(Class<? extends JR> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
private static class BaseRepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory {
private final EntityManager entityManager;
public BaseRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return new BaseRepositoryImpl<T, ID>((Class<T>) information.getDomainType(), entityManager);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
}
4. 在Application上面加上一句注解,开启处理工厂
@EnableJpaRepositories(basePackages = "com.meiyue", repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)
ublic abstract class BaseService<T, I extends Serializable, R extends BaseRepository<T, I>> {
@Autowired
R dao;
//1. 查询:一条数据
public T getOne(I id) {
return dao.findOne(id);
}
//2. 查询:数据列表 按照id升序排列
public List<T> getList() {
return dao.findAll(new Sort(Sort.Direction.ASC, "id"));
}
//3. 查询:数据分页 按照id升序排列
public PageModel<T> getPage(int page, int pageSize) {
//数据库分页查询起始id是从0开始的,请求的页码是从1开始的,所以处理的时候要减一
return page > 0 ? dao.getPage(new PageRequest(page - 1, pageSize, new Sort(Sort.Direction.ASC, "id"))) : null;
}
//4. 增加:增加一条数据
public boolean addOne(T t) {
T data = dao.save(t);
return data != null ? true : false;
}
//5. 增加:增添批量数据
public boolean addList(List<T> dataList) {
//todo 此处要做事务处理
try {
for (T t : dataList) {
addOne(t);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//6. 删除:删除一条记录
public boolean removeOne(I id) {
try {
dao.delete(id);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//7. 删除:批量删除
public boolean removeList(String ids) {
String[] idss = ids.split(",");
//todo 此处需要事务处理
try {
for (String id : idss) {
removeOne((I) id);
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//8. 修改:修改一条记录 对象必须包含id
public boolean updateOne(T t) {
try {
dao.save(t);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
在这里封装了增加和修改的操作,是方便子类的调用。在子类将对象构建好之后,就可以直接调用,也可以调用DAO进行操作,由于职司不同不建议在controller中构建对象进行调用,虽然这也可以。
三、自定义controller抽象类。严格地说,这个封装的用处不大,因为接口并不是按照数据类型分得这么清楚。不过也不排除有些接口就是操作某一种类型单一的数据,那就可以用上了,而且,可以快速实现。
由于封装要考虑KV请求和body请求两种方式,所封装的方法参数结构是不一样的,所以要分开封装,在这里只贴出KV请求的封装,body请求原理相同。
public abstract class BaseKvController<T, I extends Serializable, S extends BaseService> {
public abstract S getService();//获得Service 处理不好自动装载的笨办法
//1.获取一个数据
@RequestMapping("/getOne")
public NetResult<T> getOne(I id) {
return ResultUtils.buildResult((T)(getService().getOne(id)));
}
//2.获取数据列表
@RequestMapping("/getList")
public NetResult<List<T>> getList() {
return ResultUtils.buildResult(getService().getList());
}
//3.获取分页
@RequestMapping("/getPage")
public NetResult<PageModel<T>> getPage(int page, int pageSize) {
return ResultUtils.buildResult(getService().getPage(page, pageSize));
}
//4.删除一条
@RequestMapping("/removeOne")
public NetResult<Boolean> removeOne(I id) {
return ResultUtils.buildResult(getService().removeOne(id));
}
//5.删除一批
@RequestMapping("/removeList")
public NetResult<Boolean> removeList(String ids) {
return ResultUtils.buildResult(getService().removeList(ids));
}
}
可以看到这里有5中常见操作。
四、还有几个工具类,也一并贴出来
1.构造结果的工具类ResultUtils(见上面)
2.构造json的工具类(简易版)
public class JsonUtils {
private static Gson gson = new Gson();
public static String toJson(Object obj) {
return gson.toJson(obj);
}
}
3.打印后台日志的工具类
public class MsgUtils {
private static Logger logger = LoggerFactory.getLogger(MsgUtils.class);
public static void println(Object obj) {
System.out.println(obj);
}
public static void print(Object obj) {
System.out.print(obj);
}
public static void d(Object obj) {
logger.debug(String.valueOf(obj));
}
public static void i(Object obj) {
logger.info(String.valueOf(obj));
}
public static void w(Object obj) {
logger.warn(String.valueOf(obj));
}
public static void e(Object obj) {
logger.error(String.valueOf(obj));
}
}
我们来实践一下:
首先连接数据库
在项目上右键点击,选择Add Framework Support,选中JavaEE persistence,选中hibernate,下载确定。
这是Idea左下角会有persistence窗口,在项目上点击右键,选中Gennarate Persistence Mapping->By Database schema,选择对应的表和参数,生成自带注解的实体类,每个实体类对应一张表。
我们选择其中一个TestCitiesEntity作为我们测试的数据类型来操作。
1.创建DAO
public interface CityDao extends BaseRepository<TestCitiesEntity, Integer> {
}
可以看到,一句代码都没有,就是继承了我们封装的接口。
2.创建Service
@Service
public class CityService extends BaseService<TestCitiesEntity, Integer, CityDao> {
}
同样是一句代码都没有,注解要加上。
3.创建controller
@RestController
@RequestMapping("/city")
public class CityController extends BaseKvController<TestCitiesEntity, Integer, CityService>{
@Autowired
CityService cityService;
@Override
public CityService getService() {
return cityService;
}
}
这个我目前没解决BaseService的自动装配问题,所以留出了一个抽象方法需要实现。只加了简单的一点代码。上面的两行注解是必须的。
我们启动测试一下。数据库有可查询的数据。
我们在PostMan里请求192.168.1.101:8080/yuedao/city/getPage?page=2&pageSize=4
看看结果:
我们一个接口都没写,但是我们已经有接口可以用了。
换做那些复杂数据接口,我们也只需要把各种需要的service注入进去,进行广泛的调用组装就可以。至于那些封装顾及不到的,特事特办,已经很少了。
修改一个地方,就是controller的封装实际上是可以封装添加数据和修改一条数据的,我刚了解到,controller可以接收一个对象,而在请求的时候只需要提供相同的字段就可以了。不过,修改调用是必须要提供id的。
把下面这部分加到BaseKvController里面:
//6.添加一条数据
@RequestMapping("/addOne")
public NetResult<Boolean> addOne(T t) {
return ResultUtils.buildResult(getService().addOne(t));
}
//6.修改一条数据
@RequestMapping("/updateOne")
public NetResult<Boolean> updateOne(T t) {
return ResultUtils.buildResult(getService().updateOne(t));
}
测试没有问题,添加数据和修改数据都有效。不过由于无法判断泛型是否包含id所以无法对修改数据进行验证。如果不传id,就会增加一条数据,只有参数中包含一个可用的id,才会成功修改。