一、思考
通常而言,我們會在controller中調用service層的方法進行業務處理並給前端響應,service層在業務處理的過程中會調用repository層的接口完成數據庫操作。但是現在我們是將增刪改查等操作抽象到了父類中,在子類中直接調用這些方法完成相應操作。那麼父類在執行具體的方法時怎麼才能知道應該調用哪個子類的service或者哪個子類的repository呢?這麼問這個問題有點不好理解,舉個例子來說:上一節中我們創建了BaseService和BaseRepository兩個接口,我們都知道JPA會自動爲我們生成repository的實現,但是JPA怎麼知道具體爲哪個類生成實現——通過泛型,如果這個能夠理解的話。那下一個問題是:在BaseService的實現類中怎麼知道應該調用具體哪個類的repository呢?也就是說我們怎麼才能獲取到具體的那個類的repository對象呢?
Spring爲我們解決了這個問題。我們知道,只要在service類上加@Service註解,在controller上加@Controller等註解Spring就會爲我們創建對應類的單例對象,然後將這些對象放在Spring的ApplicationContext中,這些對象在Spring容器中的id(bean的id)是簡單類名的首字母小寫,知道了這些,我們就可以通過bean的id從Spring的ApplicationContext獲取到這些對象。因此我們需要一個從ApplicationContext中獲取bean的工具類,還需要一個將類的簡單類名轉化爲首字母小寫的工具類(用來獲取bean的id),有了這個思路就可以開始實現了。
二、工具類
從Spring的ApplicationContext中獲取bean有很多種方式,這裏採用實現ApplicationContextAware接口的方式:
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextUtil.applicationContext == null) {
SpringContextUtil.applicationContext = applicationContext;
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
將字符串首字母轉爲小寫的工具類:
public class StringUtil {
/**
* 首字母轉小寫
*
* @param str
* @return
*/
public static String firstLetterToLowerCase(String str) {
if (!StringUtils.isEmpty(str)) {
char[] chars = str.toCharArray();
chars[0] = (char) (chars[0] >= 65 && chars[0] <= 90 ? chars[0] + 32 : chars[0]);
return String.valueOf(chars);
}
return null;
}
}
三、BaseService的實現BaseAbstractService
BaseAbstractService就是我們爲BaseService提供的默認實現,也是我們整個項目的核心,之後通用的聯表查詢、通用的導入導出、通用的關鍵字搜索、通用的按指定字段統計等都會在這個類中實現。下面是我們的初步實現:
public abstract class BaseAbstractService<T extends BaseEntity> implements BaseService<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAbstractService.class);
/**
* 在Spring容器中repository對象名稱的後綴
*/
private static final String REPOSITORY_SUFFIX = "Repository";
@Override
public T saveOrUpdate(T t) {
return getRepository(t).save(t);
}
@Override
public Iterable<T> saveOrUpdateBatch(List<T> list) {
return null;
}
@Override
public void delete(T t) {
getRepository(t).delete(t);
}
@Override
public void deleteBatch(List<T> list) {
}
@Override
public T findById(T t) {
return getRepository(t).findOne(t.getId());
}
@Override
public T findOne(T t) {
Iterable<T> byEntity = findByEntity(t);
if (byEntity != null) {
return byEntity.iterator().next();
}
return null;
}
@Override
public Iterable<T> findByEntity(T t) {
return null;
}
@Override
public Iterable<T> queryAll(T t) {
return getRepository(t).findAll();
}
@Override
public Page<T> queryByPage(T t) {
return null;
}
/**
* 從Spring容器中獲取對應泛型類的repository
*
* @param t
* @return
*/
private BaseRepository<T> getRepository(T t) {
String simpleName = t.getClass().getSimpleName();
String prefix = StringUtil.firstLetterToLowerCase(simpleName);
String beanName = prefix + REPOSITORY_SUFFIX;
return (BaseRepository) SpringContextUtil.getBean(beanName);
}
}
四、BaseController的實現BaseAbstractController
public abstract class BaseAbstractController<T extends BaseEntity> extends BaseController<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAbstractController.class);
/**
* 在Spring容器中service對象名稱的後綴
*/
private static final String SERVICE_SUFFIX = "Service";
@PostMapping("/saveOrUpdate")
public RespDto saveOrUpdate(T t) {
try {
T t1 = getService(t).saveOrUpdate(t);
return success(t1);
} catch (Exception e) {
LOGGER.error("saveOrUpdate error!", e);
return error("操作異常!");
}
}
@PostMapping("/saveOrUpdateBatch")
public RespDto saveOrUpdateBatch(List<T> list) {
return null;
}
@PostMapping("/delete")
public RespDto delete(T t) {
try {
getService(t).delete(t);
return success();
} catch (Exception e) {
LOGGER.error("saveOrUpdate error!", e);
return error("操作異常!");
}
}
@PostMapping("/deleteBatch")
public RespDto deleteBatch(List<T> t) {
return null;
}
@PostMapping("/findById")
public RespDto findById(T t) {
try {
T t1 = getService(t).findById(t);
return success(t1);
} catch (Exception e) {
LOGGER.error("saveOrUpdate error!", e);
return error("操作異常!");
}
}
@PostMapping("/findOne")
public RespDto findOne(T t) {
try {
T t1 = getService(t).findOne(t);
return success(t1);
} catch (Exception e) {
LOGGER.error("saveOrUpdate error!", e);
return error("操作異常!");
}
}
@PostMapping("/findByEntity")
public RespDto findByEntity(T t) {
return null;
}
@PostMapping("/queryAll")
public RespDto queryAll(T t) {
try {
Iterable<T> ts = getService(t).queryAll(t);
return success(ts);
} catch (Exception e) {
LOGGER.error("saveOrUpdate error!", e);
return error("操作異常!");
}
}
@PostMapping("/queryByPage")
public RespDto queryByPage(T t) {
return null;
}
/**
* 從Spring容器中獲取對應泛型類的service
*
* @param t
* @return
*/
private BaseService<T> getService(T t) {
String simpleName = t.getClass().getSimpleName();
String prefix = StringUtil.firstLetterToLowerCase(simpleName);
String beanName = prefix + SERVICE_SUFFIX;
return (BaseService) SpringContextUtil.getBean(beanName);
}
}
五、測試
此時我們新建一個TestEntity繼承BaseEntity、TestEntityController繼承BaseAbstractController、TestEntityService繼承BaseAbstractService、TestEntityRepository繼承BaseRepository。
TestEntity:
@Data
@Entity
@Table(name = "test_entity")
public class TestEntity extends BaseEntity {
private static final long serialVersionUID = -8902948491649494127L;
private String name;
private Integer age;
private Integer gender;
}
TestEntityController:
@RestController
@RequestMapping("/rbl/base/test")
public class TestEntityController extends BaseAbstractController<TestEntity> {
}
TestEntityService:
@Service
@Transactional(rollbackOn = Exception.class)
public class TestEntityService extends BaseAbstractService<TestEntity> {
}
TestEntityRepository:注意這是一個接口,而且無需任何註解
public interface TestEntityRepository extends BaseRepository<TestEntity> {
}
啓動項目,測試:Idea自帶了類似於Postman的接口測試工具Rest Client,使用方式自行百度
可以看到我們在TestEntityController和TestEntityService等類中什麼都沒有做,但是卻可以執行增刪改查操作,這就是這個項目的意義所在,這樣我們就可以將一些通用的操作都集中到BaseAbstractService中實現,其他的Service就可以直接使用而不必再次開發了,是不是想想就開心呢?
這裏需要說明的是:在web項目的開發中,必然涉及到數據庫的操作,那就必然涉及到事務,我們通常會將事務加在service層,因此不要忘了在每個Service類上加@Transactional(rollbackOn = Exception.class),這裏將回滾的異常設置爲了Exception,Spring默認是在捕捉到RuntimeException時回滾事務,個人覺得設置爲Exception更合理。另外不要忘了在主啓動類上加@EnableTransactionManagement註解,否則事務是不生效的:
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = true)
public class RblBaseApplication {
public static void main(String[] args) {
SpringApplication.run(RblBaseApplication.class, args);
}
}
六、問題
上面的代碼其實有很多的不足,我們只是實現了當初設計的一部分方法。在BaseAbstractService類中,我們編寫了getRepository(T t)方法,通過它我們可以獲取到對應泛型類的repository對象(這個方法說明了泛型其實不僅僅可以讓父類知道具體的子類還能夠起到傳遞參數的作用)。但這種從Spring的容器中獲取相應repository(包含獲取相應的service)的方法有很大的侷限性——命名必須規範,否則就獲取不到。還有一個就是這種方式導致我們有些方法實現起來會比較困難,比如批量操作(這裏沒做實現),我們的方法入參設計的是傳入List,但獲取相應的repository需要的參數卻是泛型類的對象,如果我們再爲每一個批量方法增加一個參數的話又不太好,因爲這意味着controller層每次調用service層的方法都必須多傳遞一個對象,很明顯這不夠優雅。
其實Spring的存在爲我們能夠更優雅的實現這些方法提供了很大的幫助,這個我們在下一節會講到。