一、序列化
1、序列化的作用
Java平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java對(duì)象,但一般情況下,只有當(dāng)JVM處于運(yùn)行時(shí),這些對(duì)象才可能存在,即,這些對(duì)象的生命周期不會(huì)比JVM的生命周期更長(zhǎng)。但在現(xiàn)實(shí)應(yīng)用中,就可能要求在JVM停止運(yùn)行之后能夠保存(持久化)指定的對(duì)象,并在將來重新讀取被保存的對(duì)象。Java對(duì)象序列化就能夠幫助我們實(shí)現(xiàn)該功能。
2、序列化的原理
使用Java序列化,在保存對(duì)象時(shí)會(huì)將對(duì)象的狀態(tài)保存為一組字節(jié),在反序列化時(shí),又將這些字節(jié)組裝成對(duì)象,切記,對(duì)象序列化,序列化的是這個(gè)對(duì)象的狀態(tài),即,序列化的是他的成員變量,因?yàn)閷?duì)象的序列化是不關(guān)注類中的靜態(tài)變量的。在進(jìn)行序列化的時(shí)候,不會(huì)調(diào)用被序列化對(duì)象的任何構(gòu)造器。
3、序列化使用的場(chǎng)景
- 在持久化對(duì)象時(shí),使用序列化。
- 使用RMI(遠(yuǎn)程方法調(diào)用),或在網(wǎng)絡(luò)中傳輸對(duì)象時(shí),進(jìn)行對(duì)象的序列化,
4、默認(rèn)的序列化機(jī)制
如果僅僅只是讓某個(gè)類實(shí)現(xiàn)Serializable接口,而沒有其它任何處理的話,則就是使用默認(rèn)序列化機(jī)制。使用默認(rèn)機(jī)制,在序列化對(duì)象時(shí),不僅會(huì)序列化當(dāng)前對(duì)象本身,還會(huì)對(duì)該對(duì)象引用的其它對(duì)象也進(jìn)行序列化,同樣地,這些其它對(duì)象引用的另外對(duì)象也將被序列化,以此類推。所以,如果一個(gè)對(duì)象包含的成員變量是容器類對(duì)象,而這些容器所含有的元素也是容器類對(duì)象,那么這個(gè)序列化的過程就會(huì)較復(fù)雜,開銷也較大。
5、序列化的影響因素
- transient關(guān)鍵字
當(dāng)某個(gè)字段被聲明為transient后,默認(rèn)序列化機(jī)制就會(huì)忽略該字段。
public class Person implements Serializable {
...
transient private Integer age = null;
...
}
- writeObject()方法與readObject()方法
被transient修飾的屬性,如果一定需要序列化,可以在對(duì)應(yīng)的類中添加兩個(gè)方法:writeObject()與readObject()
public class Person implements Serializable {
...
transient private Integer age = null;
...
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}
}
測(cè)試類
public class SimpleSerial {
public static void main(String[] args) throws Exception {
File file = new File("person.out");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
Person person = new Person("John", 101, Gender.MALE);
oout.writeObject(person);
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject(); // 沒有強(qiáng)制轉(zhuǎn)換到Person類型
oin.close();
System.out.println(newPerson);
}
}
writeObject()方法中會(huì)先調(diào)用ObjectOutputStream中的defaultWriteObject()方法,該方法會(huì)執(zhí)行默認(rèn)的序列化機(jī)制,在進(jìn)行序列化時(shí),會(huì)先忽略掉age字段。然后再調(diào)用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的作用則是針對(duì)對(duì)象的讀取,其原理與writeObject()方法相同。
writeObject()與readObject()都是private方法,是通過反射來調(diào)用的。
- Externalizable接口
無論是使用transient關(guān)鍵字,還是使用writeObject()和readObject()方法,其實(shí)都是基于Serializable接口的序列化。JDK中提供了另一個(gè)序列化接口--Externalizable,使用該接口之后,之前基于Serializable接口的序列化機(jī)制就將失效.
public class Person implements Externalizable {
private String name = null;
transient private Integer age = null;
private Gender gender = null;
public Person() {
System.out.println("none-arg constructor");
}
public Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
...
}
輸出結(jié)果為:
arg constructor
none-arg constructor
[null, null, null]
注意:在上列的序列化中,你會(huì)發(fā)現(xiàn),沒有獲取到任何的序列化信息,且調(diào)用了Person類的午餐構(gòu)造器
Externalizable繼承于Serializable,當(dāng)使用該接口時(shí),序列化的細(xì)節(jié)需要由我們自己來完成。如上所示的代碼,由于writeExternal()與readExternal()方法未作任何處理,那么該序列化行為將不會(huì)保存/讀取任何一個(gè)字段。這也就是為什么輸出結(jié)果中所有字段的值均為空。
另外,使用Externalizable進(jìn)行序列化時(shí),當(dāng)讀取對(duì)象時(shí),會(huì)調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對(duì)象,然后再將被保存對(duì)象的字段的值分別填充到新對(duì)象中,因此,在實(shí)現(xiàn)Externalizable接口的類必須要提供一個(gè)無參的構(gòu)造器,且它的訪問權(quán)限為public。
public class Person implements Externalizable {
private String name = null;
transient private Integer age = null;
private Gender gender = null;
public Person() {
System.out.println("none-arg constructor");
}
public Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(age);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
age = in.readInt();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
...
}
輸出結(jié)果為:
arg constructor
none-arg constructor
[John, 31, null]
- readResolve()方法
當(dāng)我們使用Singleton模式時(shí),應(yīng)該是期望某個(gè)類的實(shí)例應(yīng)該是唯一的,但如果該類是可序列化的,那么情況可能略有不同。
public class Person implements Serializable {
private static class InstanceHolder {
private static final Person instatnce = new Person("John", 31, Gender.MALE);
}
public static Person getInstance() {
return InstanceHolder.instatnce;
}
private String name = null;
private Integer age = null;
private Gender gender = null;
private Person() {
System.out.println("none-arg constructor");
}
private Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
...
}
public class SimpleSerial {
public static void main(String[] args) throws Exception {
File file = new File("person.out");
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
oout.writeObject(Person.getInstance()); // 保存單例對(duì)象
oout.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
Object newPerson = oin.readObject();
oin.close();
System.out.println(newPerson);
System.out.println(Person.getInstance() == newPerson); // 將獲取的對(duì)象與Person類中的單例對(duì)象進(jìn)行相等性比較
}
}
輸出結(jié)果:
arg constructor
[John, 31, MALE]
false
此時(shí),你會(huì)發(fā)現(xiàn),它已經(jīng)不再是一個(gè)單利對(duì)象,失去了單利的性質(zhì),為了能在序列化過程仍能保持單例的特性,可以在Person類中添加一個(gè)readResolve()方法,在該方法中直接返回Person的單例對(duì)象
public class Person implements Serializable {
private static class InstanceHolder {
private static final Person instatnce = new Person("John", 31, Gender.MALE);
}
public static Person getInstance() {
return InstanceHolder.instatnce;
}
private String name = null;
private Integer age = null;
private Gender gender = null;
private Person() {
System.out.println("none-arg constructor");
}
private Person(String name, Integer age, Gender gender) {
System.out.println("arg constructor");
this.name = name;
this.age = age;
this.gender = gender;
}
private Object readResolve() throws ObjectStreamException {
return InstanceHolder.instatnce;
}
...
}
當(dāng)再次執(zhí)行上方測(cè)試類的時(shí)候,執(zhí)行結(jié)果為:
arg constructor
[John, 31, MALE]
true
無論是實(shí)現(xiàn)Serializable接口,或是Externalizable接口,當(dāng)從I/O流中讀取對(duì)象時(shí),readResolve()方法都會(huì)被調(diào)用到。實(shí)際上就是用readResolve()中返回的對(duì)象直接替換在反序列化過程中創(chuàng)建的對(duì)象。
二、序列化知識(shí)結(jié)晶
1、序列化ID(serialVersionUID )的作用
private static final long serialVersionUID = 1L;
虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)
2、靜態(tài)變量序列化問題
public class Test implements Serializable {
private static final long serialVersionUID = 1L;
public static int staticVar = 5;
public static void main(String[] args) {
try {
//初始時(shí)staticVar為5
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
//序列化后修改為10
Test.staticVar = 10;
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
oin.close();
//再讀取,通過t.staticVar打印新的值
System.out.println(t.staticVar);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 輸出結(jié)果為10
最后的輸出是 10,對(duì)于無法理解的讀者認(rèn)為,打印的 staticVar 是從讀取的對(duì)象里獲得的,應(yīng)該是保存時(shí)的狀態(tài)才對(duì)。之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài),因此 序列化并不保存靜態(tài)變量。
3、父類序列化問題
要想將父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口。如果父類不實(shí)現(xiàn)的話的,就 需要有默認(rèn)的無參的構(gòu)造函數(shù)。
在父類沒有實(shí)現(xiàn) Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè) Java 對(duì)象的構(gòu)造必須先有父對(duì)象,才有子對(duì)象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對(duì)象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認(rèn)的父對(duì)象。因此當(dāng)我們?nèi)「笇?duì)象的變量值時(shí),它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況,在父類無參構(gòu)造函數(shù)中對(duì)變量進(jìn)行初始化,否則的話,父類變量值都是默認(rèn)聲明的值,如 int 型的默認(rèn)是 0,string 型的默認(rèn)是 null。
4、Transient序列化問題
Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null。
5、對(duì)敏感字段加密
- 場(chǎng)景
服務(wù)器端給客戶端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。
- 原理
在序列化過程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值?;谶@個(gè)原理,可以在實(shí)際應(yīng)用中得到使用,用于敏感字段的加密工作
private static final long serialVersionUID = 1L;
private String password = "pass";
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// 對(duì)密碼進(jìn)行了加密
private void writeObject(ObjectOutputStream out) {
try {
PutField putFields = out.putFields();
System.out.println("原密碼:" + password);
password = "encryption";//模擬加密
putFields.put("password", password);
System.out.println("加密后的密碼" + password);
out.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
// 對(duì) password 進(jìn)行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全。
private void readObject(ObjectInputStream in) {
try {
GetField readFields = in.readFields();
Object object = readFields.get("password", "");
System.out.println("要解密的字符串:" + object.toString());
password = "pass";//模擬解密,需要獲得本地的密鑰
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("result.obj"));
out.writeObject(new Test());
out.close();
ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
"result.obj"));
Test t = (Test) oin.readObject();
System.out.println("解密后的字符串:" + t.getPassword());
oin.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
三、序列化跟反序列化常識(shí)注意點(diǎn)
1、在Java中,只要一個(gè)類實(shí)現(xiàn)了java.io.Serializable接口,那么它就可以被序列化。
2、通過ObjectOutputStream和ObjectInputStream對(duì)對(duì)象進(jìn)行序列化及反序列化
3、虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4、序列化并不保存靜態(tài)變量。
5、要想將父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口。
6、Transient 關(guān)鍵字的作用是控制變量的序列化,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設(shè)為初始值,如 int 型的是 0,對(duì)象型的是 null。
7、服務(wù)器端給客戶端發(fā)送序列化對(duì)象數(shù)據(jù),對(duì)象中有一些數(shù)據(jù)是敏感的,比如密碼字符串等,希望對(duì)該密碼字段在序列化時(shí),進(jìn)行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進(jìn)行反序列化時(shí),才可以對(duì)密碼進(jìn)行讀取,這樣可以一定程度保證序列化對(duì)象的數(shù)據(jù)安全。
8、在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化。如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。
四、實(shí)例
1、 進(jìn)行反序列化,并忽略某些不需要反序列化的屬性
package com.qianfan123.mbr.service.report;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.hd123.rumba.commons.biz.entity.EntityNotFoundException;
import com.hd123.rumba.commons.biz.entity.OperateContext;
import com.hd123.rumba.commons.biz.entity.OperateInfo;
import com.hd123.rumba.commons.biz.entity.Operator;
import com.hd123.rumba.commons.biz.entity.StandardEntity;
import com.hd123.rumba.commons.biz.entity.VersionConflictException;
import com.hd123.rumba.commons.lang.Assert;
import com.qianfan123.mbr.annotation.Tx;
import com.qianfan123.mbr.api.MbrException;
import com.qianfan123.mbr.api.common.Nsid;
import com.qianfan123.mbr.api.consumption.Consumption;
import com.qianfan123.mbr.api.member.Member;
import com.qianfan123.mbr.dao.report.ReportEntity;
import com.qianfan123.mbr.dao.report.statistic.StatisticHistory;
import com.qianfan123.mbr.dao.report.statistic.StatisticHistoryDao;
import com.qianfan123.mbr.service.report.target.MemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.MemberOccuredAtDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtConsumptionDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyConsumptionDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyMemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyRegisteredDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtMemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.TargetMarker;
import com.qianfan123.mbr.service.report.target.TenantConsumptionDataTargerMarker;
import com.qianfan123.mbr.service.report.target.TenantMemberDataTargerMarker;
import com.qianfan123.mbr.service.report.util.StatisticDataReverseUtils;
import com.qianfan123.mbr.utils.EntityUtils;
/**
* 報(bào)表統(tǒng)計(jì)服務(wù)抽象類
*
* @author huzexiong
*
*/
@Component
public abstract class AbstractStatisticService<S extends StandardEntity> //
implements StatisticService, ApplicationContextAware {
private final Logger logger = LoggerFactory.getLogger(AbstractStatisticService.class);
private ApplicationContext applicationContext;
// 根據(jù)統(tǒng)計(jì)來源來獲取統(tǒng)計(jì)元數(shù)據(jù)工廠
private Map<Class<?>, MakerFactory> makerFactories = new HashMap<Class<?>, MakerFactory>();
// 報(bào)表目標(biāo)數(shù)據(jù)
private List<TargetMarker> targetMarkers = new ArrayList<TargetMarker>();
// 用于反序列化歷史統(tǒng)計(jì)數(shù)據(jù)對(duì)象
private static final ObjectMapper MAPPER;
static {
MAPPER = new ObjectMapper();
MAPPER.setSerializationInclusion(NON_NULL);
MAPPER.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
MAPPER.addMixIn(Member.class, MemberMixIn.class);
final SimpleModule module = new SimpleModule();
module.addDeserializer(Nsid.class, new JsonDeserializer<Nsid>() {
@Override
public Nsid deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.readValueAsTree();
return new Nsid(node.get("namespace").asText(), node.get("id").asText());
}
});
MAPPER.registerModule(module);
}
/**
* 在反序列化時(shí),忽略Member中的: fetchParts 屬性 因?yàn)?fetchParts 對(duì)應(yīng)兩個(gè)set方法,反序列化會(huì)沖突
*
* @author wangxiaofeng
*
*/
static abstract class MemberMixIn {
@JsonIgnore
abstract Set<String> getFetchParts();
}
@Autowired
private StatisticHistoryDao statisticHistoryDao;
@PostConstruct
public void init() {
makerFactories.put(Member.class, applicationContext.getBean(MemberMakerFactory.class));
makerFactories.put(Consumption.class,
applicationContext.getBean(ConsumptionMakerFactory.class));
targetMarkers.add(applicationContext.getBean(MemberDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(TenantConsumptionDataTargerMarker.class));
targetMarkers.add(applicationContext.getBean(TenantMemberDataTargerMarker.class));
targetMarkers.add(applicationContext.getBean(OccuredAtConsumptionDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(OccuredAtMemberDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(OccuredAtDailyConsumptionDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(OccuredAtDailyMemberDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(OccuredAtDailyRegisteredDataTargetMarker.class));
targetMarkers.add(applicationContext.getBean(MemberOccuredAtDataTargetMarker.class));
}
/**
* 獲取統(tǒng)計(jì)數(shù)據(jù)源
*
* @param tenant
* 租戶, not null
* @param uuid
* UUID, not null
* @return
*/
public abstract S get(String tenant, String uuid);
public abstract Class<?> getClassType();
@Tx
@Override
public void run(String tenant, String uuid) throws MbrException, VersionConflictException {
Assert.hasText(tenant);
Assert.hasText(uuid);
// 查詢統(tǒng)計(jì)歷史信息
StatisticHistory statisticHistory = statisticHistoryDao.get(tenant, uuid);
S last = restore(statisticHistory);
// 根據(jù)不同的統(tǒng)計(jì)來源獲取對(duì)應(yīng)的元數(shù)據(jù)工廠
MakerFactory makerFactory = makerFactories.get(getClassType());
try {
// 先對(duì)歷史數(shù)據(jù)進(jìn)行反向操作,并持久化
if (null != last) {
builder(last, makerFactory, -1);
}
// 獲取統(tǒng)計(jì)源
S source = get(tenant, uuid);
if (null != source) {
builder(source, makerFactory, 1);
}
if (null == last && null == source) {
logger.info("未查詢到源數(shù)據(jù)和歷史數(shù)據(jù):tenant[{}],uuid[{}],class[{}],統(tǒng)計(jì)忽略。", //
tenant, uuid, getClassType().getName());
return;
}
// 統(tǒng)計(jì)完成,記錄歷史。
if (null == last) {// 新增
insertHistory(tenant, source);
} else if (null != source) {// 更新
modifyHistory(statisticHistory, source);
} else {// 刪除
removeHistory(statisticHistory);
}
} catch (Exception e) {
e.printStackTrace();
logger.error("報(bào)表統(tǒng)計(jì)異常:" + e.getMessage());
throw new MbrException(e);
}
}
/**
* 對(duì)獲取到的歷史統(tǒng)計(jì)數(shù)據(jù)信息進(jìn)行反序列化操作
*
* @param statisticHistory
* @return
* @throws MbrException
*/
private S restore(StatisticHistory statisticHistory) throws MbrException {
if (null == statisticHistory) {
return null;
}
S last = null;
try {
last = (S) MAPPER.readValue(statisticHistory.getData(), getClassType());
} catch (IOException e) {
throw new MbrException(e);
}
return last;
}
/**
* 生成報(bào)表數(shù)據(jù)并進(jìn)行持久化操作
*
* @param source
* 統(tǒng)計(jì)數(shù)據(jù)源,not null
* @param makerFactory
* 相應(yīng)的統(tǒng)計(jì)元數(shù)據(jù)工廠 , not null
* @param base
* 1 或 -1 ,表示是歷史數(shù)據(jù)還是新的統(tǒng)計(jì)源數(shù)據(jù)
*/
private void builder(S source, MakerFactory makerFactory, int base) throws Exception,
VersionConflictException, IllegalArgumentException, EntityNotFoundException {
// 根據(jù)傳入的統(tǒng)計(jì)來源,產(chǎn)生相應(yīng)的元數(shù)據(jù)列表
List<Object> metaDatas = makerFactory.makeValues(source);
// base = -1 表示是歷史表中的統(tǒng)計(jì)數(shù)據(jù)
if (-1 == base) {
StatisticDataReverseUtils.reverse(metaDatas, -1);
}
// 進(jìn)行報(bào)表數(shù)據(jù)更新與保存等操作
for (TargetMarker targetMarker : targetMarkers) {
if (getClassType() != targetMarker.getSourceType()) {
continue;
}
// 根據(jù)元統(tǒng)計(jì)數(shù)據(jù)和來源數(shù)據(jù)生成報(bào)表數(shù)據(jù)
ReportEntity report = targetMarker.make(source, metaDatas);
if (null == report) {
continue;
}
// 獲取數(shù)據(jù)庫(kù)統(tǒng)計(jì)數(shù)據(jù)信息
ReportEntity db = targetMarker.getReportEntityDao().getBy(report);
// 如果第一次生成報(bào)表數(shù)據(jù)信息,則進(jìn)行插入操作,否則,先進(jìn)行數(shù)據(jù)合并,再更新
if (null == db) {
EntityUtils.setOperateInfo(report, getOperationContext());
targetMarker.getReportEntityDao().insert(report);
} else {
// 合并數(shù)據(jù)
ReportEntity merge = targetMarker.merge(report, db);
EntityUtils.setOperateInfo(merge, getOperationContext());
targetMarker.getReportEntityDao().update(merge);
}
}
}
/**
* 插入歷史統(tǒng)計(jì)數(shù)據(jù)
*
* @param tenant
* 租戶
* @param entity
* 相應(yīng)實(shí)體
* @return
* @throws JsonProcessingException
*/
private StatisticHistory insertHistory(String tenant, S entity) throws JsonProcessingException {
StatisticHistory his = new StatisticHistory();
his.setTenant(tenant);
his.setStatisticUuid(entity.getUuid());
his.setLastUpdated(getLastUpdated(entity));
his.setData(MAPPER.writeValueAsString(entity));
EntityUtils.setOperateInfo(his, getOperationContext());
statisticHistoryDao.insert(his);
return his;
}
private Date getLastUpdated(S entity) {
OperateInfo operateInfo = entity.getLastModifyInfo();
Assert.notNull(operateInfo);
return operateInfo.getTime();
}
private void modifyHistory(StatisticHistory his, S entity)
throws JsonProcessingException, VersionConflictException {
his.setLastUpdated(getLastUpdated(entity));
his.setData(MAPPER.writeValueAsString(entity));
EntityUtils.setLastModifyInfo(his, getOperationContext());
statisticHistoryDao.update(his);
}
private void removeHistory(StatisticHistory his) throws VersionConflictException {
statisticHistoryDao.delete(his);
}
protected OperateContext getOperationContext() {
OperateContext context = new OperateContext();
context.setOperator(new Operator("統(tǒng)計(jì)報(bào)表", null));
context.setTime(new Date());
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}