欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringAop實(shí)現(xiàn)操作日志記錄

 更新時(shí)間:2020年12月29日 17:34:58   作者:經(jīng)典雞翅  
這篇文章主要介紹了SpringAop實(shí)現(xiàn)操作日志記錄的方法,幫助大家更好的理解和使用SpringAop,感興趣的朋友可以了解下

前言

大家好,這里是經(jīng)典雞翅,今天給大家?guī)?lái)一篇基于SpringAop實(shí)現(xiàn)的操作日志記錄的解決的方案。大家可能會(huì)說(shuō),切,操作日志記錄這么簡(jiǎn)單的東西,老生常談了。不!

網(wǎng)上的操作日志一般就是記錄操作人,操作的描述,ip等。好一點(diǎn)的增加了修改的數(shù)據(jù)和執(zhí)行時(shí)間。那么!我這篇有什么不同呢!今天這種不僅可以記錄上方所說(shuō)的一切,還增加記錄了操作前的數(shù)據(jù),錯(cuò)誤的信息,堆棧信息等。正文開(kāi)始~~~~~

思路介紹

記錄操作日志的操作前數(shù)據(jù)是需要思考的重點(diǎn)。我們以修改場(chǎng)景來(lái)作為探討。當(dāng)我們要完全記錄數(shù)據(jù)的流向的時(shí)候,我們必然要記錄修改前的數(shù)據(jù),而前臺(tái)進(jìn)行提交的時(shí)候,只有修改的數(shù)據(jù),那么如何找到修改前的數(shù)據(jù)呢。有三個(gè)大的要素,我們需要知道修改前數(shù)據(jù)的表名,表的字段主鍵,表主鍵的值。這樣通過(guò)這三個(gè)屬性,我們可以很容易的拼出 select * from 表名 where 主鍵字段 = 主鍵值。我們就獲得了修改前的數(shù)據(jù),轉(zhuǎn)換為json之后就可以存入到數(shù)據(jù)庫(kù)中了。如何獲取三個(gè)屬性就是重中之重了。我們采取的方案是通過(guò)提交的映射實(shí)體,在實(shí)體上打上注解,根據(jù) Java 的反射取到值。再進(jìn)一步拼裝獲得對(duì)象數(shù)據(jù)。那么AOP是在哪里用的呢,我們需要在記錄操作日志的方法上,打上注解,再通過(guò)切面獲取到切點(diǎn),一切的數(shù)據(jù)都通過(guò)反射來(lái)進(jìn)行獲得。

定義操作日志注解

既然是基于spinrg的aop實(shí)現(xiàn)切面。那么必然是需要一個(gè)自定義注解的。用來(lái)作為切點(diǎn)。我們定義的注解,可以帶一些必要的屬性,例如操作的描述,操作的類(lèi)型。操作的類(lèi)型需要說(shuō)一下,我們分為新增、修改、刪除、查詢(xún)。那么只有修改和刪除的時(shí)候,我們需要查詢(xún)一下修改前的數(shù)據(jù)。其他兩種是不需要的,這個(gè)也可以用來(lái)作為判斷。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {

   String operation() default "";

   String operateType() default "";

}

定義用于找到表和表主鍵的注解

表和表主鍵的注解打在實(shí)體上,內(nèi)部有兩個(gè)屬性 tableName 和 idName。這兩個(gè)屬性的值獲得后,可以進(jìn)行拼接 select * from 表名 where 主鍵字段。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelectTable {

	String tableName() default "";

	String idName() default "";
}

定義獲取主鍵值的注解

根據(jù)上面所說(shuō)的三個(gè)元素,我們還缺最后一個(gè)元素主鍵值的獲取,用于告訴我們,我們應(yīng)該從提交的請(qǐng)求的那個(gè)字段,拿到其中的值。

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelectPrimaryKey {

}

注解的總結(jié)

有了上面的三個(gè)注解,注解的準(zhǔn)備工作已經(jīng)進(jìn)行完畢。我們通過(guò)反射取到數(shù)據(jù),可以獲得一切。接下來(lái)開(kāi)始實(shí)現(xiàn)切面,對(duì)于注解的值進(jìn)行拼接處理,最終存入到我們的數(shù)據(jù)庫(kù)操作日志表中。

切面的實(shí)現(xiàn)

對(duì)于切面來(lái)說(shuō),我們需要實(shí)現(xiàn)切點(diǎn)、數(shù)據(jù)庫(kù)的插入、反射的數(shù)據(jù)獲取。我們先分開(kāi)進(jìn)行解釋?zhuān)詈蠼o出全面的實(shí)現(xiàn)代碼。方便大家的理解和學(xué)習(xí)。

切面的定義

基于spring的aspect進(jìn)行聲明這是一個(gè)切面。

@Aspect
@Component
public class OperateLogAspect {
}

切點(diǎn)的定義

切點(diǎn)就是對(duì)所有的打上OperateLog的注解的請(qǐng)求進(jìn)行攔截和加強(qiáng)。我們使用annotation進(jìn)行攔截。

	@Pointcut("@annotation(com.jichi.aop.operateLog.OperateLog)")
	private void operateLogPointCut(){
	}

獲取請(qǐng)求ip的共用方法

	private String getIp(HttpServletRequest request){
		String ip = request.getHeader("X-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

數(shù)據(jù)庫(kù)的日志插入操作

我們將插入數(shù)據(jù)庫(kù)的日志操作進(jìn)行單獨(dú)的抽取。

private void insertIntoLogTable(OperateLogInfo operateLogInfo){
	operateLogInfo.setId(UUID.randomUUID().toString().replace("-",""));
	String sql="insert into log values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
	jdbcTemplate.update(sql,operateLogInfo.getId(),operateLogInfo.getUserId(),
		operateLogInfo.getUserName(),operateLogInfo.getOperation(),operateLogInfo.getMethod(),
		operateLogInfo.getModifiedData(),operateLogInfo.getPreModifiedData(),
		operateLogInfo.getResult(),operateLogInfo.getErrorMessage(),operateLogInfo.getErrorStackTrace(),
		operateLogInfo.getExecuteTime(),operateLogInfo.getDuration(),operateLogInfo.getIp(),
		operateLogInfo.getModule(),operateLogInfo.getOperateType());
}

環(huán)繞通知的實(shí)現(xiàn)

日志的實(shí)體類(lèi)實(shí)現(xiàn)

@TableName("operate_log")
@Data
public class OperateLogInfo {

	//主鍵id
	@TableId
	private String id;
	//操作人id
	private String userId;
	//操作人名稱(chēng)
	private String userName;
	//操作內(nèi)容
	private String operation;
	//操作方法名稱(chēng)
	private String method;
	//操作后的數(shù)據(jù)
	private String modifiedData;
	//操作前數(shù)據(jù)
	private String preModifiedData;
	//操作是否成功
	private String result;
	//報(bào)錯(cuò)信息
	private String errorMessage;
	//報(bào)錯(cuò)堆棧信息
	private String errorStackTrace;
	//開(kāi)始執(zhí)行時(shí)間
	private Date executeTime;
	//執(zhí)行持續(xù)時(shí)間
	private Long duration;
	//ip
	private String ip;
	//操作類(lèi)型
	private String operateType;

}

準(zhǔn)備工作全部完成。接下來(lái)的重點(diǎn)是對(duì)環(huán)繞通知的實(shí)現(xiàn)。思路分為數(shù)據(jù)處理、異常捕獲、finally執(zhí)行數(shù)據(jù)庫(kù)插入操作。環(huán)繞通知的重點(diǎn)類(lèi)就是ProceedingJoinPoint ,我們通過(guò)它的getSignature方法可以獲取到打在方法上注解的值。例如下方。

MethodSignature signature = (MethodSignature) pjp.getSignature();
OperateLog declaredAnnotation = signature.getMethod().getDeclaredAnnotation(OperateLog.class);
operateLogInfo.setOperation(declaredAnnotation.operation());
operateLogInfo.setModule(declaredAnnotation.module());
operateLogInfo.setOperateType(declaredAnnotation.operateType());
//獲取執(zhí)行的方法
String method = signature.getDeclaringType().getName() + "." + signature.getName();
operateLogInfo.setMethod(method);
String operateType = declaredAnnotation.operateType();

獲取請(qǐng)求的數(shù)據(jù),也是通過(guò)這個(gè)類(lèi)來(lái)實(shí)現(xiàn),這里有一點(diǎn)是需要注意的,就是我們要約定參數(shù)的傳遞必須是第一個(gè)參數(shù)。這樣才能保證我們?nèi)〉降臄?shù)據(jù)是提交的數(shù)據(jù)。

if(pjp.getArgs().length>0){
	Object args = pjp.getArgs()[0];
	operateLogInfo.setModifiedData(new Gson().toJson(args));
}

接下來(lái)的一步就是對(duì)修改前的數(shù)據(jù)進(jìn)行拼接。之前我們提到過(guò)如果是修改和刪除,我們才會(huì)進(jìn)行數(shù)據(jù)的拼接獲取,主要是通過(guò)類(lèi)來(lái)判斷書(shū)否存在注解,如果存在注解,那么就要判斷注解上的值是否是控制或者,非空才能正確的進(jìn)行拼接。取field的值的時(shí)候,要注意私有的變量需要通過(guò)setAccessible(true)才可以進(jìn)行訪問(wèn)。

if(GlobalStaticParas.OPERATE_MOD.equals(operateType) ||
	GlobalStaticParas.OPERATE_DELETE.equals(operateType)){
	String tableName = "";
	String idName = "";
	String selectPrimaryKey = "";
	if(pjp.getArgs().length>0){
		Object args = pjp.getArgs()[0];
		//獲取操作前的數(shù)據(jù)
		boolean selectTableFlag = args.getClass().isAnnotationPresent(SelectTable.class);
		if(selectTableFlag){
			tableName = args.getClass().getAnnotation(SelectTable.class).tableName();
			idName = args.getClass().getAnnotation(SelectTable.class).idName();
		}else {
			throw new RuntimeException("操作日志類(lèi)型為修改或刪除,實(shí)體類(lèi)必須指定表面和主鍵注解!");
		}
		Field[] fields = args.getClass().getDeclaredFields();
		Field[] fieldsCopy = fields;
		boolean isFindField = false;
		int fieldLength = fields.length;
		for(int i = 0; i < fieldLength; ++i) {
			Field field = fieldsCopy[i];
			boolean hasPrimaryField = field.isAnnotationPresent(SelectPrimaryKey.class);
			if (hasPrimaryField) {
				isFindField = true;
				field.setAccessible(true);
				selectPrimaryKey = (String)field.get(args);
			}
		}
		if(!isFindField){
			throw new RuntimeException("實(shí)體類(lèi)必須指定主鍵屬性!");
		}
	}
	if(StringUtils.isNotEmpty(tableName) &&
		StringUtils.isNotEmpty(idName)&&
		StringUtils.isNotEmpty(selectPrimaryKey)){
		StringBuffer sb = new StringBuffer();
		sb.append(" select * from ");
		sb.append(tableName);
		sb.append(" where ");
		sb.append(idName);
		sb.append(" = ? ");
		String sql = sb.toString();
		try{
			List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, selectPrimaryKey);
			if(maps!=null){
				operateLogInfo.setPreModifiedData(new Gson().toJson(maps));
			}
		}catch (Exception e){
			e.printStackTrace();
			throw new RuntimeException("查詢(xún)操作前數(shù)據(jù)出錯(cuò)!");
		}
	}else {
		throw new RuntimeException("表名、主鍵名或主鍵值 存在空值情況,請(qǐng)核實(shí)!");
	}
}else{
	operateLogInfo.setPreModifiedData("");
}

切面的完整實(shí)現(xiàn)代碼

@Aspect
@Component
public class OperateLogAspect {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Pointcut("@annotation(com.jichi.aop.operateLog.OperateLog)")
	private void operateLogPointCut(){
	}

	@Around("operateLogPointCut()")
	public Object around(ProceedingJoinPoint pjp) throws Throwable {
		Object responseObj = null;
		OperateLogInfo operateLogInfo = new OperateLogInfo();
		String flag = "success";
		try{
			HttpServletRequest request = SpringContextUtil.getHttpServletRequest();
			DomainUserDetails currentUser = SecurityUtils.getCurrentUser();
			if(currentUser!=null){
				operateLogInfo.setUserId(currentUser.getId());
				operateLogInfo.setUserName(currentUser.getUsername());
			}
			MethodSignature signature = (MethodSignature) pjp.getSignature();
			OperateLog declaredAnnotation = signature.getMethod().getDeclaredAnnotation(OperateLog.class);
			operateLogInfo.setOperation(declaredAnnotation.operation());
			operateLogInfo.setModule(declaredAnnotation.module());
			operateLogInfo.setOperateType(declaredAnnotation.operateType());
			//獲取執(zhí)行的方法
			String method = signature.getDeclaringType().getName() + "." + signature.getName();
			operateLogInfo.setMethod(method);
			String operateType = declaredAnnotation.operateType();
			if(pjp.getArgs().length>0){
				Object args = pjp.getArgs()[0];
				operateLogInfo.setModifiedData(new Gson().toJson(args));
			}
			if(GlobalStaticParas.OPERATE_MOD.equals(operateType) ||
				GlobalStaticParas.OPERATE_DELETE.equals(operateType)){
				String tableName = "";
				String idName = "";
				String selectPrimaryKey = "";
				if(pjp.getArgs().length>0){
					Object args = pjp.getArgs()[0];
					//獲取操作前的數(shù)據(jù)
					boolean selectTableFlag = args.getClass().isAnnotationPresent(SelectTable.class);
					if(selectTableFlag){
						tableName = args.getClass().getAnnotation(SelectTable.class).tableName();
						idName = args.getClass().getAnnotation(SelectTable.class).idName();
					}else {
						throw new RuntimeException("操作日志類(lèi)型為修改或刪除,實(shí)體類(lèi)必須指定表面和主鍵注解!");
					}
					Field[] fields = args.getClass().getDeclaredFields();
					Field[] fieldsCopy = fields;
					boolean isFindField = false;
					int fieldLength = fields.length;
					for(int i = 0; i < fieldLength; ++i) {
						Field field = fieldsCopy[i];
						boolean hasPrimaryField = field.isAnnotationPresent(SelectPrimaryKey.class);
						if (hasPrimaryField) {
							isFindField = true;
							field.setAccessible(true);
							selectPrimaryKey = (String)field.get(args);
						}
					}
					if(!isFindField){
						throw new RuntimeException("實(shí)體類(lèi)必須指定主鍵屬性!");
					}
				}
				if(StringUtils.isNotEmpty(tableName) &&
					StringUtils.isNotEmpty(idName)&&
					StringUtils.isNotEmpty(selectPrimaryKey)){
					StringBuffer sb = new StringBuffer();
					sb.append(" select * from ");
					sb.append(tableName);
					sb.append(" where ");
					sb.append(idName);
					sb.append(" = ? ");
					String sql = sb.toString();
					try{
						List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, selectPrimaryKey);
						if(maps!=null){
							operateLogInfo.setPreModifiedData(new Gson().toJson(maps));
						}
					}catch (Exception e){
						e.printStackTrace();
						throw new RuntimeException("查詢(xún)操作前數(shù)據(jù)出錯(cuò)!");
					}
				}else {
					throw new RuntimeException("表名、主鍵名或主鍵值 存在空值情況,請(qǐng)核實(shí)!");
				}
			}else{
				operateLogInfo.setPreModifiedData("");
			}
			//操作時(shí)間
			Date beforeDate = new Date();
			Long startTime = beforeDate.getTime();
			operateLogInfo.setExecuteTime(beforeDate);
			responseObj = pjp.proceed();
			Date afterDate = new Date();
			Long endTime = afterDate.getTime();
			Long duration = endTime - startTime;
			operateLogInfo.setDuration(duration);
			operateLogInfo.setIp(getIp(request));
			operateLogInfo.setResult(flag);
		}catch (RuntimeException e){
			throw new RuntimeException(e);
		}catch (Exception e){
			flag = "fail";
			operateLogInfo.setResult(flag);
			operateLogInfo.setErrorMessage(e.getMessage());
			operateLogInfo.setErrorStackTrace(e.getStackTrace().toString());
			e.printStackTrace();
		}finally {
			insertIntoLogTable(operateLogInfo);
		}
		return responseObj;
	}

	private void insertIntoLogTable(OperateLogInfo operateLogInfo){
		operateLogInfo.setId(UUID.randomUUID().toString().replace("-",""));
		String sql="insert into energy_log values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
		jdbcTemplate.update(sql,operateLogInfo.getId(),operateLogInfo.getUserId(),
			operateLogInfo.getUserName(),operateLogInfo.getOperation(),operateLogInfo.getMethod(),
			operateLogInfo.getModifiedData(),operateLogInfo.getPreModifiedData(),
			operateLogInfo.getResult(),operateLogInfo.getErrorMessage(),operateLogInfo.getErrorStackTrace(),
			operateLogInfo.getExecuteTime(),operateLogInfo.getDuration(),operateLogInfo.getIp(),
			operateLogInfo.getModule(),operateLogInfo.getOperateType());
	}

	private String getIp(HttpServletRequest request){
		String ip = request.getHeader("X-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_CLIENT_IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}
}

示例的使用方式

針對(duì)于示例來(lái)說(shuō)我們要在controller上面打上操作日志的注解。

  @PostMapping("/updateInfo")
  @OperateLog(operation = "修改信息",operateType = GlobalStaticParas.OPERATE_MOD)
  public void updateInfo(@RequestBody Info info) {
    service.updateInfo(info);
  }

針對(duì)于Info的實(shí)體類(lèi),我們則要對(duì)其中的字段和表名進(jìn)行標(biāo)識(shí)。

@Data
@SelectTable(tableName = "info",idName = "id")
public class Info {

  @SelectPrimaryKey
  private String id;
  
  private String name;

}

總結(jié)

文章寫(xiě)到這,也就結(jié)束了,文中難免有不足,歡迎大家批評(píng)指正

以上就是SpringAop實(shí)現(xiàn)操作日志記錄的詳細(xì)內(nèi)容,更多關(guān)于SpringAop 操作日志記錄的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot實(shí)現(xiàn)文件下載的四種方式

    SpringBoot實(shí)現(xiàn)文件下載的四種方式

    本文主要介紹了SpringBoot實(shí)現(xiàn)文件下載的四種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 關(guān)于Spring?Validation數(shù)據(jù)校檢的使用流程分析

    關(guān)于Spring?Validation數(shù)據(jù)校檢的使用流程分析

    在實(shí)際項(xiàng)目中,對(duì)客戶(hù)端傳遞到服務(wù)端的參數(shù)進(jìn)行校驗(yàn)至關(guān)重要,SpringValidation提供了一種便捷的方式來(lái)實(shí)現(xiàn)這一需求,通過(guò)在POJO類(lèi)的屬性上添加檢查注解,本文給大家介紹Spring?Validation數(shù)據(jù)校檢的使用流程,感興趣的朋友一起看看吧
    2024-11-11
  • SpringBoot靜態(tài)資源目錄訪問(wèn)

    SpringBoot靜態(tài)資源目錄訪問(wèn)

    今天小編就為大家分享一篇關(guān)于SpringBoot靜態(tài)資源目錄訪問(wèn),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Java之SpringBean生命周期問(wèn)題理解

    Java之SpringBean生命周期問(wèn)題理解

    這篇文章主要介紹了Java之SpringBean生命周期問(wèn)題理解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • mybatis-plus自帶QueryWrapper自定義sql實(shí)現(xiàn)復(fù)雜查詢(xún)實(shí)例詳解

    mybatis-plus自帶QueryWrapper自定義sql實(shí)現(xiàn)復(fù)雜查詢(xún)實(shí)例詳解

    MyBatis-Plus是一個(gè)MyBatis(opens new window)的增強(qiáng)工具,在 MyBatis的基礎(chǔ)上只做增強(qiáng)不做改變,MyBatis可以無(wú)損升級(jí)為MyBatis-Plus,這篇文章主要給大家介紹了關(guān)于mybatis-plus自帶QueryWrapper自定義sql實(shí)現(xiàn)復(fù)雜查詢(xún)的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源

    SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源

    本文主要介紹了SpringBoot多數(shù)據(jù)源配置并通過(guò)注解實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 通過(guò)實(shí)例解析Java不可變對(duì)象原理

    通過(guò)實(shí)例解析Java不可變對(duì)象原理

    這篇文章主要介紹了通過(guò)實(shí)例解析Java不可變對(duì)象原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Java中你絕對(duì)沒(méi)用過(guò)的一個(gè)關(guān)鍵字Record的使用

    Java中你絕對(duì)沒(méi)用過(guò)的一個(gè)關(guān)鍵字Record的使用

    這篇文章主要給大家介紹一個(gè)?Java?中的一個(gè)關(guān)鍵字?Record,那?Record?關(guān)鍵字跟不可變類(lèi)有什么關(guān)系呢?看完今天的文章你就知道了,快跟隨小編一起學(xué)習(xí)一下吧
    2022-11-11
  • hibernate 配置數(shù)據(jù)庫(kù)方言的實(shí)現(xiàn)方法

    hibernate 配置數(shù)據(jù)庫(kù)方言的實(shí)現(xiàn)方法

    這篇文章主要介紹了hibernate 配置數(shù)據(jù)庫(kù)方言的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Java中l(wèi)ambda表達(dá)式的基本運(yùn)用

    Java中l(wèi)ambda表達(dá)式的基本運(yùn)用

    大家好,本篇文章主要講的是Java中l(wèi)ambda表達(dá)式的基本運(yùn)用,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下
    2022-01-01

最新評(píng)論