其實在 Spring 上要做 JOIN,還挺麻煩的…
如果可以的話,我覺得能不要做就不要做比較好 XD
不過真的需要的時候,如果是需要 SELECT 時做 JOIN,推薦可以參考 [1-2] 的說明。
實務應用的話,假設以 logback 輸出到 SQL Server 的格式作為範例
想要一次取得整個 log 包含相關的 property 和 stack trace,以下的 Entity 範例是能夠套用。
logback 輸出 SQL Server 的 schema
schema 可以參考 [3],以下為 schema 的內容:
CREATE TABLE logging_event
(
timestmp DECIMAL(20) NOT NULL,
formatted_message VARCHAR(4000) NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id DECIMAL(40) NOT NULL identity,
PRIMARY KEY(event_id)
)
CREATE TABLE logging_event_property
(
event_id DECIMAL(40) NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value VARCHAR(1024),
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
)
CREATE TABLE logging_event_exception
(
event_id DECIMAL(40) NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
)
Entity
假設我的 Entity 物件名字是 Activity、ActivityProperty、ActivityStackTrace,分別定義如下:
Activity
@Entity(name = "logging_event")
public class Activity implements Serializable {
@Id
@Column(name = "event_id")
private Long eventId;
@Column(name = "timestmp")
private Long timestmp;
@Column(name = "formatted_message")
private String message;
@Column(name = "logger_name")
private String logger;
@Column(name = "level_string")
private String level;
@Column(name = "thread_name")
private String thread;
@Column(name = "reference_flag")
private Integer reference;
@Column(name = "arg0")
private String arg0;
@Column(name = "arg1")
private String arg1;
@Column(name = "arg2")
private String arg2;
@Column(name = "arg3")
private String arg3;
@Column(name = "caller_filename")
private String callerFile;
@Column(name = "caller_class")
private String callerClass;
@Column(name = "caller_method")
private String callerMethod;
@Column(name = "caller_line")
private String callerLine;
@OneToMany(
mappedBy = "eventId",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER)
private List<ActivityProperty> properties;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "event_id", referencedColumnName = "event_id")
@LazyCollection(LazyCollectionOption.FALSE)
private List<ActivityStackTrace> stackTraces;
}
ActivityProperty
@Entity(name = "logging_event_property")
public class ActivityProperty implements Serializable {
private static final long serialVersionUID = -6532428503805543364L;
@Id
@Column(name = "event_id")
private Long eventId;
@Column(name = "mapped_key")
private String key;
@Column(name = "mapped_value")
private String value;
}
ActivityStackTrace
@Entity(name = "logging_event_exception")
public class ActivityStackTrace implements Serializable {
private static final long serialVersionUID = -50856985039937262L;
@EmbeddedId
private ActivityStackTraceId id;
@Column(name = "trace_line")
private String line;
}
ActivityStackTraceId
@Embeddable
public class ActivityStackTraceId implements Serializable {
private static final long serialVersionUID = -4496556483758827756L;
@Column(name = "event_id")
private Long eventId;
@Column(name = "i")
private Integer i;
}
其中在 Activity 要連結 ActivityProperty 和 ActivityStackTrace 時,都是以 @OneToMany 來標注
用來表示一個 Activity 可以有多個 ActivityProperty 和 ActivityStackTrace。
此外,由於 ActivityStackTrace 並非是單一 ID,而是 Composite Primary Key 的形式
因此要利用 @Embeddable來組合。這部份可以參考 [4]。
同時,使用了 @Embeddable 之後,如果 JOIN 操作依然使用 mappedBy 的話,會出現 [5] 提到的錯誤
目前其實還沒有搞很懂這個錯誤是什麼意思,不過姑且使用 [5] 的作法,改成用 LazyCollection 可以解決。
未來如果有把問題搞懂的話,再回來追加補充了 @@a
除此之外,還得稍微注意一下,上述的 JOIN 寫法,有可能導致 [6] 描述的問題
也就是當要做資料刪除時,Hibernate 產生的 SQL Statement 會用很沒效率的方式在刪除關聯
而且某些狀況下有可能會刪除沒有預期要刪除的資料。
參考資料
- JPA,在@OneToMany里加入mappedBy属性
- hibernate基于注解的维护权反转:@OneToMany(mappedBy=)
- mssql.sql
- The best way to map a Composite Primary Key with JPA and Hibernate
- Hibernate cannot simultaneously fetch multiple bags
- Why you should avoid CascadeType.REMOVE for to-many associations and what to do instead
- @JoinColumn 详解