一疹鳄、序列化
1、序列化的作用
Java平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java對象篓冲,但一般情況下李破,只有當JVM處于運行時,這些對象才可能存在壹将,即嗤攻,這些對象的生命周期不會比JVM的生命周期更長。但在現(xiàn)實應(yīng)用中诽俯,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象妇菱,并在將來重新讀取被保存的對象。Java對象序列化就能夠幫助我們實現(xiàn)該功能惊畏。
2恶耽、序列化的原理
使用Java序列化,在保存對象時會將對象的狀態(tài)保存為一組字節(jié)颜启,在反序列化時偷俭,又將這些字節(jié)組裝成對象,切記缰盏,對象序列化涌萤,序列化的是這個對象的狀態(tài),即口猜,序列化的是他的成員變量负溪,因為對象的序列化是不關(guān)注類中的靜態(tài)變量的。在進行序列化的時候济炎,不會調(diào)用被序列化對象的任何構(gòu)造器川抡。
3、序列化使用的場景
- 在持久化對象時须尚,使用序列化崖堤。
- 使用RMI(遠程方法調(diào)用),或在網(wǎng)絡(luò)中傳輸對象時耐床,進行對象的序列化密幔,
4、默認的序列化機制
如果僅僅只是讓某個類實現(xiàn)Serializable接口撩轰,而沒有其它任何處理的話胯甩,則就是使用默認序列化機制昧廷。使用默認機制,在序列化對象時偎箫,不僅會序列化當前對象本身木柬,還會對該對象引用的其它對象也進行序列化,同樣地淹办,這些其它對象引用的另外對象也將被序列化弄诲,以此類推。所以娇唯,如果一個對象包含的成員變量是容器類對象齐遵,而這些容器所含有的元素也是容器類對象,那么這個序列化的過程就會較復(fù)雜塔插,開銷也較大梗摇。
5、序列化的影響因素
- transient關(guān)鍵字
當某個字段被聲明為transient后想许,默認序列化機制就會忽略該字段伶授。
public class Person implements Serializable {
...
transient private Integer age = null;
...
}
- writeObject()方法與readObject()方法
被transient修飾的屬性,如果一定需要序列化流纹,可以在對應(yīng)的類中添加兩個方法: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();
}
}
測試類
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(); // 沒有強制轉(zhuǎn)換到Person類型
oin.close();
System.out.println(newPerson);
}
}
writeObject()方法中會先調(diào)用ObjectOutputStream中的defaultWriteObject()方法糜烹,該方法會執(zhí)行默認的序列化機制,在進行序列化時,會先忽略掉age字段漱凝。然后再調(diào)用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中疮蹦。readObject()的作用則是針對對象的讀取,其原理與writeObject()方法相同茸炒。
writeObject()與readObject()都是private方法愕乎,是通過反射來調(diào)用的。
- Externalizable接口
無論是使用transient關(guān)鍵字壁公,還是使用writeObject()和readObject()方法感论,其實都是基于Serializable接口的序列化。JDK中提供了另一個序列化接口--Externalizable紊册,使用該接口之后比肄,之前基于Serializable接口的序列化機制就將失效.
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]
注意:在上列的序列化中,你會發(fā)現(xiàn)囊陡,沒有獲取到任何的序列化信息芳绩,且調(diào)用了Person類的午餐構(gòu)造器
Externalizable繼承于Serializable,當使用該接口時关斜,序列化的細節(jié)需要由我們自己來完成示括。如上所示的代碼铺浇,由于writeExternal()與readExternal()方法未作任何處理痢畜,那么該序列化行為將不會保存/讀取任何一個字段。這也就是為什么輸出結(jié)果中所有字段的值均為空。
另外丁稀,使用Externalizable進行序列化時吼拥,當讀取對象時,會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個新的對象线衫,然后再將被保存對象的字段的值分別填充到新對象中凿可,因此,在實現(xiàn)Externalizable接口的類必須要提供一個無參的構(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()方法
當我們使用Singleton模式時,應(yīng)該是期望某個類的實例應(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()); // 保存單例對象
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); // 將獲取的對象與Person類中的單例對象進行相等性比較
}
}
輸出結(jié)果:
arg constructor
[John, 31, MALE]
false
此時屋确,你會發(fā)現(xiàn)纳击,它已經(jīng)不再是一個單利對象,失去了單利的性質(zhì)攻臀,為了能在序列化過程仍能保持單例的特性焕数,可以在Person類中添加一個readResolve()方法,在該方法中直接返回Person的單例對象
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;
}
...
}
當再次執(zhí)行上方測試類的時候刨啸,執(zhí)行結(jié)果為:
arg constructor
[John, 31, MALE]
true
無論是實現(xiàn)Serializable接口狗超,或是Externalizable接口,當從I/O流中讀取對象時暇屋,readResolve()方法都會被調(diào)用到炫隶。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創(chuàng)建的對象。
二仑荐、序列化知識結(jié)晶
1雕拼、序列化ID(serialVersionUID )的作用
private static final long serialVersionUID = 1L;
虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致粘招,一個非常重要的一點是兩個類的序列化 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 {
//初始時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洒扎,對于無法理解的讀者認為辑甜,打印的 staticVar 是從讀取的對象里獲得的,應(yīng)該是保存時的狀態(tài)才對袍冷。之所以打印 10 的原因在于序列化時磷醋,并不保存靜態(tài)變量,這其實比較容易理解胡诗,序列化保存的是對象的狀態(tài)邓线,靜態(tài)變量屬于類的狀態(tài)淌友,因此 序列化并不保存靜態(tài)變量。
3骇陈、父類序列化問題
要想將父類對象也序列化震庭,就需要讓父類也實現(xiàn)Serializable 接口。如果父類不實現(xiàn)的話的你雌,就 需要有默認的無參的構(gòu)造函數(shù)器联。
在父類沒有實現(xiàn) Serializable 接口時,虛擬機是不會序列化父對象的婿崭,而一個 Java 對象的構(gòu)造必須先有父對象拨拓,才有子對象,反序列化也不例外氓栈。所以反序列化時千元,為了構(gòu)造父對象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認的父對象颤绕。因此當我們?nèi)「笇ο蟮淖兞恐禃r幸海,它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況奥务,在父類無參構(gòu)造函數(shù)中對變量進行初始化物独,否則的話,父類變量值都是默認聲明的值氯葬,如 int 型的默認是 0挡篓,string 型的默認是 null。
4帚称、Transient序列化問題
Transient 關(guān)鍵字的作用是控制變量的序列化官研,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中闯睹,在被反序列化后戏羽,transient 變量的值被設(shè)為初始值,如 int 型的是 0楼吃,對象型的是 null始花。
5、對敏感字段加密
- 場景
服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)孩锡,對象中有一些數(shù)據(jù)是敏感的酷宵,比如密碼字符串等,希望對該密碼字段在序列化時躬窜,進行加密浇垦,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時荣挨,才可以對密碼進行讀取男韧,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全朴摊。
- 原理
在序列化過程中,虛擬機會試圖調(diào)用對象類里的 writeObject 和 readObject 方法煌抒,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法厕倍,則默認調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法寡壮。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態(tài)改變序列化的數(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;
}
// 對密碼進行了加密
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();
}
}
// 對 password 進行解密,只有擁有密鑰的客戶端棒仍,才可以正確的解析出密碼,確保了數(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();
}
}
三莫其、序列化跟反序列化常識注意點
1、在Java中耸三,只要一個類實現(xiàn)了java.io.Serializable接口乱陡,那么它就可以被序列化。
2仪壮、通過ObjectOutputStream和ObjectInputStream對對象進行序列化及反序列化
3憨颠、虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致积锅,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
4爽彤、序列化并不保存靜態(tài)變量。
5缚陷、要想將父類對象也序列化适篙,就需要讓父類也實現(xiàn)Serializable 接口。
6箫爷、Transient 關(guān)鍵字的作用是控制變量的序列化匙瘪,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中蝶缀,在被反序列化后丹喻,transient 變量的值被設(shè)為初始值,如 int 型的是 0翁都,對象型的是 null碍论。
7、服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)柄慰,對象中有一些數(shù)據(jù)是敏感的鳍悠,比如密碼字符串等税娜,希望對該密碼字段在序列化時,進行加密藏研,而客戶端如果擁有解密的密鑰敬矩,只有在客戶端進行反序列化時,才可以對密碼進行讀取蠢挡,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全弧岳。
8、在序列化過程中业踏,如果被序列化的類中定義了writeObject 和 readObject 方法禽炬,虛擬機會試圖調(diào)用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化勤家。如果沒有這樣的方法腹尖,則默認調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程伐脖,比如可以在序列化的過程中動態(tài)改變序列化的數(shù)值热幔。
四、實例
1讼庇、 進行反序列化断凶,并忽略某些不需要反序列化的屬性
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;
/**
* 報表統(tǒng)計服務(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)計來源來獲取統(tǒng)計元數(shù)據(jù)工廠
private Map<Class<?>, MakerFactory> makerFactories = new HashMap<Class<?>, MakerFactory>();
// 報表目標數(shù)據(jù)
private List<TargetMarker> targetMarkers = new ArrayList<TargetMarker>();
// 用于反序列化歷史統(tǒng)計數(shù)據(jù)對象
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);
}
/**
* 在反序列化時,忽略Member中的: fetchParts 屬性 因為 fetchParts 對應(yīng)兩個set方法巫俺,反序列化會沖突
*
* @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)計數(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)計歷史信息
StatisticHistory statisticHistory = statisticHistoryDao.get(tenant, uuid);
S last = restore(statisticHistory);
// 根據(jù)不同的統(tǒng)計來源獲取對應(yīng)的元數(shù)據(jù)工廠
MakerFactory makerFactory = makerFactories.get(getClassType());
try {
// 先對歷史數(shù)據(jù)進行反向操作介汹,并持久化
if (null != last) {
builder(last, makerFactory, -1);
}
// 獲取統(tǒng)計源
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)計忽略却嗡。", //
tenant, uuid, getClassType().getName());
return;
}
// 統(tǒng)計完成,記錄歷史嘹承。
if (null == last) {// 新增
insertHistory(tenant, source);
} else if (null != source) {// 更新
modifyHistory(statisticHistory, source);
} else {// 刪除
removeHistory(statisticHistory);
}
} catch (Exception e) {
e.printStackTrace();
logger.error("報表統(tǒng)計異常:" + e.getMessage());
throw new MbrException(e);
}
}
/**
* 對獲取到的歷史統(tǒng)計數(shù)據(jù)信息進行反序列化操作
*
* @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;
}
/**
* 生成報表數(shù)據(jù)并進行持久化操作
*
* @param source
* 統(tǒng)計數(shù)據(jù)源窗价,not null
* @param makerFactory
* 相應(yīng)的統(tǒng)計元數(shù)據(jù)工廠 , not null
* @param base
* 1 或 -1 叹卷,表示是歷史數(shù)據(jù)還是新的統(tǒng)計源數(shù)據(jù)
*/
private void builder(S source, MakerFactory makerFactory, int base) throws Exception,
VersionConflictException, IllegalArgumentException, EntityNotFoundException {
// 根據(jù)傳入的統(tǒng)計來源撼港,產(chǎn)生相應(yīng)的元數(shù)據(jù)列表
List<Object> metaDatas = makerFactory.makeValues(source);
// base = -1 表示是歷史表中的統(tǒng)計數(shù)據(jù)
if (-1 == base) {
StatisticDataReverseUtils.reverse(metaDatas, -1);
}
// 進行報表數(shù)據(jù)更新與保存等操作
for (TargetMarker targetMarker : targetMarkers) {
if (getClassType() != targetMarker.getSourceType()) {
continue;
}
// 根據(jù)元統(tǒng)計數(shù)據(jù)和來源數(shù)據(jù)生成報表數(shù)據(jù)
ReportEntity report = targetMarker.make(source, metaDatas);
if (null == report) {
continue;
}
// 獲取數(shù)據(jù)庫統(tǒng)計數(shù)據(jù)信息
ReportEntity db = targetMarker.getReportEntityDao().getBy(report);
// 如果第一次生成報表數(shù)據(jù)信息,則進行插入操作骤竹,否則帝牡,先進行數(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)計數(shù)據(jù)
*
* @param tenant
* 租戶
* @param entity
* 相應(yīng)實體
* @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)計報表", null));
context.setTime(new Date());
return context;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
- 參考文章如下: