- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我从 Hibernate 迁移到 EclipseLink,因为我们需要 EclipseLink 可以很好地处理复合主键,而 Hibernate 则不能(确实不能!)。现在我正在修复我们的 JUnit 测试,但我遇到了未加载大量 OneToMany 关系的问题。
我有以下类(class):
DatabaseSession.java
package platform.data;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import platform.accesscontrol.UserContext;
import pm.data.IndicatorSet;
/**
* Provides easy to use database sessions and transactions.
* <p>
* The session and transaction is automatically opened in the constructor.
* <p>
* The session must be closed using close(), which should be done with a try(...) { ...} block. If data is modified,
* the transaction must be committed explicitly using commit(), usually as the last statement in the
* try(...) { ...} block. Uncommitted transactions are automatically rolled back when the session is closed.
*/
public final class DatabaseSession implements AutoCloseable {
/**
* Maximum latency in milliseconds for a JPA operation, after which a warning shall be logged.
*/
private static final double MAX_LATENCY = 100.0;
/**
* Maximum duration in milliseconds for a session, after which a warning shall be logged.
*/
private static final double MAX_LATENCY_TOT = 1000.0;
/**
* Our logger, never null.
*/
private static final Logger log = LoggerFactory.getLogger(DatabaseSession.class);
/**
* The factory for creating EntityManager instances, created in initEntityManagerFactory() or in the constructor.
*/
private static EntityManagerFactory factory;
/**
* The EntityManager instance to access the database, created from the factory in the constructor.
*/
private EntityManager em;
/**
* The time when the instance was created, useful for measure total time of the session.
*/
private final long ttot = System.nanoTime();
/**
* Indicates whether commit() as been called.
*/
private boolean committed;
/**
* Initializes the EntityManagerFactory (optional, useful for testing).
* <p>
* If this method is not called, the EntityManagerFactory is initialized
* automatically with persistence unit "default" when the first instance is created.
* <p>
* Persistence units are defined in conf/META-INF/persistence.xml.
*
* @param persistenceUnitName the name of the persistence unit to be used,
* must match the XML attribute /persistence/persistence-unit/@name.
*/
public static void initEntityManagerFactory(String persistenceUnitName) {
synchronized(DatabaseSession.class) {
factory = Persistence.createEntityManagerFactory(persistenceUnitName);
}
}
public void shutdownDB(){
em.close();
em = null;
DatabaseSession.factory.close();
DatabaseSession.factory = null;
}
/**
* Opens a new session and begins a new transaction.
*/
public DatabaseSession() {
synchronized(DatabaseSession.class) {
if(factory == null) {
factory = Persistence.createEntityManagerFactory("default");
}
}
createEntityManager();
}
public void createEntityManager(){
em = factory.createEntityManager();
em.getTransaction().begin();
EntityType<IndicatorSet> entity = factory.getMetamodel().entity(IndicatorSet.class);
Set<Attribute<IndicatorSet, ?>> attrs = entity.getDeclaredAttributes();
attrs.toString();
}
@Override
public void close() {
try {
if (!committed) {
if(em != null){
em.getTransaction().rollback();
}
}
} finally {
if (committed) {
if(em != null){
em.close();
}
}
double latency = (System.nanoTime() - ttot)/1000000.0;
if(latency > MAX_LATENCY_TOT) {
log.warn("Duration of session was " + latency + "ms.");
} else {
log.debug("Duration of session was " + latency + "ms.");
}
}
}
/**
* Commits the transaction, must explicitly be done before the session is closed.
*/
public void commit()
{
long t = System.nanoTime();
em.flush();
em.getTransaction().commit();
committed = true;
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of commit() was %sms.", latency);
}
}
public <T extends PersistentRecord> List<T> loadAll(Class<T> clazz, String mandt) {
return loadAll(clazz, mandt, true);
}
public <T extends PersistentRecord> List<T> loadAll(Class<T> clazz, String mandt, boolean filterDeleted) {
log("loadAll(%s)", clazz.getSimpleName());
long t = System.nanoTime();
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<T> q = b.createQuery(clazz);
Metamodel m = em.getMetamodel();
EntityType<T> et = m.entity(clazz);
Root<T> r = q.from(clazz);
q.select(r);
if (mandt != null) {
q.where(b.equal(r.get(et.getAttribute("mandt").getName()), mandt));
}
if (filterDeleted) {
q.where(b.equal(r.get(et.getAttribute("deleted").getName()), 0));
}
List<T> result = em.createQuery(q).getResultList();
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of loadAll(%s) was %sms.", clazz.getSimpleName(), latency);
}
return result;
}
public <T extends PersistentRecord> int count(Class<T> clazz, String mandt) {
return count(clazz, mandt, true);
}
public <T extends PersistentRecord> int count(Class<T> clazz, String mandt, boolean filterDeleted) {
log("count(%s)", clazz.getSimpleName());
long t = System.nanoTime();
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<T> q = b.createQuery(clazz);
Metamodel m = em.getMetamodel();
EntityType<T> et = m.entity(clazz);
Root<T> r = q.from(clazz);
q.select(r);
if (mandt != null) {
q.where(b.equal(r.get(et.getAttribute("mandt").getName()), mandt));
}
if (filterDeleted) {
q.where(b.equal(r.get(et.getAttribute("deleted").getName()), 0));
}
List<T> result = em.createQuery(q).getResultList();
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of count(%s) was %sms.", clazz.getSimpleName(), latency);
}
return result.size();
}
public <T extends PersistentRecord> T load(Class<T> clazz, String mandt, String id) {
return load(clazz, mandt, id, true);
}
public <T extends PersistentRecord> T load(Class<T> clazz, String mandt, String id, boolean filterDeleted) {
log("load(%s, %s)", clazz.getSimpleName(), id);
long t = System.nanoTime();
T result = em.find(clazz, mandt != null ? new MandtId(mandt, id) : id);
if(result != null){
em.refresh(result); // TODO: This always results in a database hit, but relationship syncing is not meant to be done that way. Reduction of db hits can be achieved trough custom annotation or flag.
//JPA does not maintain relationships for you, the application is required to set both sides to stay in sync (http://stackoverflow.com/questions/16762004/eclipselink-bidirectional-onetomany-relation)"
}
if(filterDeleted) {
result = filterDeleted(result);
}
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of load(%s, %s) was %sms.", clazz.getSimpleName(), id, latency);
}
return result;
}
public <T extends PersistentRecord> List<T> loadByQuery(Class<T> clazz, String mandt, String query, Object... params) {
log("loadByQuery(%s, '%s', %s)", clazz.getSimpleName(), query, format(params));
long t = System.nanoTime();
TypedQuery<T> q = em.createQuery(query, clazz);
for(int i = 0; i < params.length; i++) {
q.setParameter(i+1, params[i]);
}
List<T> result = q.getResultList();
if (mandt != null) { // mandt can be null to allow queries without mandt
result = filterMandt(result, mandt); // as a safety measure we ensure mandt separation in db and application layer
}
result = filterDeleted(result);
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of loadByQuery(%s, '%s', %s) was %sms.", clazz.getSimpleName(), query, format(params), latency);
}
return result;
}
public <T extends PersistentRecord> T loadSingleByQuery(Class<T> clazz, String mandt, String query, Object... params) {
log("loadSingleByQuery(%s, '%s', %s)", clazz.getSimpleName(), query, format(params));
long t = System.nanoTime();
TypedQuery<T> q = em.createQuery(query, clazz);
for(int i = 0; i < params.length; i++) {
q.setParameter(i+1, params[i]);
}
List<T> result = q.getResultList();
if (mandt != null) { // mandt can be null to allow queries without mandt
result = filterMandt(result, mandt); // as a safety measure we ensure mandt separation in db and application layer
}
result = filterDeleted(result);
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of loadSingleByQuery(%s, '%s', %s) was %sms.", clazz.getSimpleName(), query, format(params), latency);
}
return result.size() > 0 ? result.get(0) : null;
}
/**
* Stores a new or updated record (resulting in an INSERT or UPDATE statement)
* @param record the record to be stored, must not be null.
* @param uc the user that initiated the operation, can be null.
* @return the given record, or another instance with the same ID if EntityManager.merge() was called.
*/
public <T extends PersistentRecord> T store(T record, UserContext uc) {
if(record == null) {
return null;
}
log("update(%s, %s)", record.getClass().getSimpleName(), record.getId());
if(record instanceof ReadWriteRecord) {
((ReadWriteRecord)record).touch(uc);
}
return add(record);
}
/**
* Deletes a record or marks a record as deleted (resulting in an UPDATE or maybe an INSERT statement if T is a subclass of ReadWriteRecord, or resulting in a DELETE statement otherwise).
* @param record the record to be deleted, must not be null.
* @param uc the user that initiated the operation, can be null.
* @return the given record, or another instance with the same ID if EntityManager.merge() was called.
*/
public <T extends PersistentRecord> T delete(T record, UserContext uc) {
if(record == null) {
return null;
}
log("delete(%s, %s)", record.getClass().getSimpleName(), record.getId());
if(record instanceof ReadWriteRecord) {
((ReadWriteRecord)record).setDeleted(true);
((ReadWriteRecord)record).touch(uc);
return add(record); // same as store(), we _dont_ physically delete the record
} else {
em.remove(record);
return null;
}
}
/**
* Physically deletes all records of a table, intended for JUnit tests only (unless you really want to get rid of your data).
* @param clazz the DTO class of the table.
*/
public <T extends PersistentRecord> void deleteAll(Class<T> clazz, String mandt) {
log("deleteAll(%s)", clazz.getSimpleName());
for(T rec : loadAll(clazz, mandt, false)) {
em.remove(rec);
}
}
/**
* Forces lazy initialization of an entity.
* @param record a record loaded from the database, can be null.
* @return the record passed to this method.
*/
public <T extends PersistentRecord> T fetch(T record) {
if(record != null) {
em.refresh(record);// TODO: This always results in a database hit, but relationship syncing is not meant to be done that way. Reduction of db hits can be achieved trough custom annotation or flag.
//JPA does not maintain relationships for you, the application is required to set both sides to stay in sync (http://stackoverflow.com/questions/16762004/eclipselink-bidirectional-onetomany-relation)
record.fetch();
}
return record;
}
/**
* Forces lazy initialization of an entity.
* @param record a record loaded from the database, can be null.
* @param fetcher a method to be invoked on the record to lazy initialize nested fields.
* @return the record passed to this method.
*/
public <T extends PersistentRecord> T fetch(T record, BiConsumer<DatabaseSession, T> fetcher) {
if(record != null) {
em.refresh(record); // TODO: This always results in a database hit, but relationship syncing is not meant to be done that way. Reduction of db hits can be achieved trough custom annotation or flag.
//JPA does not maintain relationships for you, the application is required to set both sides to stay in sync (http://stackoverflow.com/questions/16762004/eclipselink-bidirectional-onetomany-relation)
record.fetch();
fetcher.accept(this, record);
}
return record;
}
/**
* Forces lazy initialization of multiple entities.
* @param records a list of records loaded from the database, can be null.
* @param fetcher a method to be invoked on the records to lazy initialize nested fields.
* @return the list of records passed to this method.
*/
public <T extends PersistentRecord> List<T> fetch(List<T> records, BiConsumer<DatabaseSession, T> fetcher) {
if(records != null) {
for(T record : records) {
em.refresh(record); // TODO: This always results in a database hit, but relationship syncing is not meant to be done that way. Reduction of db hits can be achieved trough custom annotation or flag.
//JPA does not maintain relationships for you, the application is required to set both sides to stay in sync (http://stackoverflow.com/questions/16762004/eclipselink-bidirectional-onetomany-relation)
record.fetch();
fetcher.accept(this, record);
}
}
return records;
}
/**
* Forces lazy initialization of a one-to-many relationship.
* @param records a list representing a one-to-many relationship, can be null.
* @return the relationship passed to this method.
*/
public <T extends PersistentRecord> List<T> fetchCollection(List<T> records) {
if(records != null) {
records.size();
}
return records;
}
/**
* Adds the given record to the EntityManager, called by store() and delete().
* <p>
* This method attempts to do something like Hibernate's saveOrUpdate(), which is not available in JPA:
* <ul>
* <li> For newly created records, EntityManager.persist() has to be called in order so insert the record.
* This case will be assumed when markNew() has been called on the record.
* <li> For records that have been read from the database by _another_ session (so-called detached entities),
* EntityManager.merge() has to be called in order to update the record.
* This case will be assumed when markNew() has NOT been called on the record.
* <li> For records that have been read from the database by this session, nothing has to be done because the
* EntityManager takes care of the entities it loaded. This case can be detected easily using contains().
* </ul>
* Note: EntityManager.merge() does not add the entity to the session.
* Instead, a new entity is created and all properties are copied from the given record to the new entity.
*
* @param record the record to be added, can be null.
* @return the given record, or another instance with the same ID if EntityManager.merge() was called.
*/
private <T extends PersistentRecord> T add(T record) {
long t = System.nanoTime();
try {
if (record == null || em.contains(record)) {
return record;
} else if(record.mustInsert) {
em.persist(record); // will result in INSERT
record.mustInsert = false;
return record;
} else {
record = em.merge(record);
return record;
}
} finally {
double latency = (System.nanoTime() - t)/1000000.0;
if(latency > MAX_LATENCY) {
warn("Latency of add(%s, %s) was %sms.", record.getClass().getSimpleName(), record.getId(), latency);
}
}
}
private static <T extends PersistentRecord> List<T> filterDeleted(List<T> records) {
if(records != null) {
records = records.stream().
filter(record -> (record instanceof ReadWriteRecord) == false || ((ReadWriteRecord) record).getDeleted() == false).
collect(Collectors.toList());
}
return records;
}
private static <T extends PersistentRecord> List<T> filterMandt(List<T> records, String mandt) {
if(records != null) {
records = records.stream().
filter(record -> Objects.equals(record.getMandt(), mandt)).
collect(Collectors.toList());
}
return records;
}
private static <T extends PersistentRecord> T filterDeleted(T record) {
if(record != null && record instanceof ReadWriteRecord) {
if(((ReadWriteRecord) record).getDeleted()) {
record = null;
}
}
return record;
}
private void log(String format, Object... args) {
if(log.isDebugEnabled()) {
log.debug(String.format(format, args));
}
}
private void warn(String format, Object... args) {
if(log.isWarnEnabled()) {
log.warn(String.format(format, args));
}
}
private static String format(Object... args) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for(Object arg: args) {
if(sb.length() > 1)
sb.append(", ");
sb.append(arg);
}
sb.append("]");
return sb.toString();
}
// For debugging
public Query createQuery(String string) {
return em.createQuery(string);
}
}
Project.java
package pm.data;
...common imports...
import platform.data.DatabaseBindingIds;
import platform.data.MandtId;
import platform.data.PropertySet;
import platform.data.ReadWriteRecord;
import resource.data.Resource;
@Entity
@IdClass(MandtId.class)
public class Project extends ReadWriteRecord {
@Id
@Column(name=DatabaseBindingIds.PROJECT_TENANT)
private String mandt;
@Id
@Column(name=DatabaseBindingIds.PROJECT_ID)
private String entityId;
@OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.ALL) // one to one mappings are directly mapped using the project primary keys
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROJECT_TENANT, referencedColumnName=DatabaseBindingIds.PROPERTYSET_TENANT, insertable=false, updatable=false),
@JoinColumn(name=DatabaseBindingIds.PROJECT_ID, referencedColumnName=DatabaseBindingIds.PROPERTYSET_ID, insertable=false, updatable=false)
} )
private PropertySet propertySet;
@OneToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL) // one to one mappings are directly mapped using the project report primary keys
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROJECTREPORT_TENANT, referencedColumnName=DatabaseBindingIds.INDICATORSET_TENANT, insertable=false, updatable=false),
@JoinColumn(name=DatabaseBindingIds.PROJECTREPORT_ID, referencedColumnName=DatabaseBindingIds.INDICATORSET_ID, insertable=false, updatable=false)
} )
private IndicatorSet indicatorSet; // SAMPLE NOTE: The indicator set is essentially the same thing as the property set.
...other member variables...
@Override
public String getMandt() {
return mandt;
}
@Override
public String getId() {
return entityId;
}
@Override
public void setId(MandtId x) {
markNew();
mandt = x != null ? x.getMandt() : null;
entityId = x != null ? x.getId() : null;
propertySet = new PropertySet();
propertySet.setId(x);
}
public PropertySet getPropertySet() {
return propertySet;
}
...getters and setters for other member variables...
}
PropertySet.java
package platform.data;
import java.util.ArrayList;
import java.util.List;
...common imports...
@Entity
@IdClass(MandtId.class)
public class PropertySet extends ReadWriteRecord {
@Id
@Column(name=DatabaseBindingIds.PROPERTYSET_TENANT)
private String mandt;
@Id
@Column(name=DatabaseBindingIds.PROPERTYSET_ID)
private String entityId;
@OneToMany(mappedBy="propertySet", fetch=FetchType.EAGER)
@OrderBy("sortIndex")
private List<Property> properties;
@Override
public String getMandt() {
return mandt;
}
@Override
public String getId() {
return entityId;
}
@Override
public void setId(MandtId x) {
markNew();
mandt = x != null ? x.getMandt() : null;
entityId = x != null ? x.getId() : null;
}
public List<Property> getProperties() {
if(properties == null) {
properties = new ArrayList<>();
}
return properties;
}
}
Property.java
package platform.data;
...common imports...
@Entity
@IdClass(MandtId.class)
public class Property extends ReadWriteRecord {
@Id
@Column(name=DatabaseBindingIds.PROPERTY_TENANT)
private String mandt;
@Id
@Column(name=DatabaseBindingIds.PROPERTY_ID)
private String entityId;
@ManyToOne(fetch=FetchType.EAGER, optional=false)
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROPERTY_TENANT, referencedColumnName=DatabaseBindingIds.PROPERTYSET_TENANT, insertable=false, updatable=false),
@JoinColumn(name=DatabaseBindingIds.PROPERTY_PROPERTYSET_ID, referencedColumnName=DatabaseBindingIds.PROPERTYSET_ID, insertable=true, updatable=true)
} )
private PropertySet propertySet;
@Column
private Integer sortIndex;
@Column
private String key;
@Column
@Convert(converter = IntlStringConverter.class)
private IntlString label;
@Column
private String type;
@Column
private String value;
@Override
public String getMandt() {
return mandt;
}
@Override
public String getId() {
return entityId;
}
@Override
public void setId(MandtId x) {
markNew();
mandt = x != null ? x.getMandt() : null;
entityId = x != null ? x.getId() : null;
}
public void setPropertySet(PropertySet x) {
propertySet = x;
}
public PropertySet getPropertySet() {
return propertySet;
}
public int getSortIndex() {
return sortIndex == null ? 0 : sortIndex;
}
public void setSortIndex(int x) {
sortIndex = x;
}
public String getKey() {
return key;
}
public void setKey(String x) {
key = x;
}
public IntlString getLabel() {
return label;
}
public void setLabel(IntlString x) {
label = x;
}
public String getType() {
return type;
}
public void setType(String x) {
type = x;
}
public String getValue() {
return value;
}
public void setValue(String x) {
value = x;
}
}
MandtId.java复合主键IDClass。
package platform.data;
import java.io.Serializable;
import java.util.Objects;
/**
* @author sm
* Class to map MANDT and *ID field as composite key
*/
@SuppressWarnings("serial")
public class MandtId implements Serializable {
private String mandt;
private String entityId;
...setters and getters...
@Override
public int hashCode()
...
@Override
public boolean equals(Object other)
...
@Override
public String toString()
...
}
我们在每个单元测试之前插入条目,如下所示:
try(DatabaseSession db = new DatabaseSession()) {
Project prjT = createProject(db, UUID_PROJECT_NEW, "<New Project>");
createProperty(db, prjT.getPropertySet(), "prj-prop1", "Property 1", "string", "<New Value 1>", 2);
createProperty(db, prjT.getPropertySet(), "prj-prop2", "Property 2", "string", "<New Value 2>", 1);
db.commit();
}
public static Project createProject(DatabaseSession db, String id, String name) {
Project prj = new Project();
prj.setId(new MandtId(MANDT, id));
prj.setName(name);
prj.setStatus(UUID_PROJECT_STATUS_ACTIVE);
db.store(prj.getPropertySet(), null); // workaround: persist child first (otherwise PropertySet will stay marked as new)
db.store(prj, null);
return prj;
}
public static Property createProperty(DatabaseSession db, PropertySet ps, String key, String label, String type, String value, int sortIndex) {
Property rec = new Property();
rec.setId(new MandtId(MANDT, UuidString.generateNew()));
rec.setPropertySet(ps);
rec.setKey(key);
rec.setLabel(IntlStrings.wrap(label));
rec.setType(type);
rec.setValue(value);
rec.setSortIndex(sortIndex);
ps.getProperties().add(rec);
db.store(rec.getPropertySet(), null);
db.store(rec, null);
// rec.properties.add(p);
return rec;
}
如果我稍后尝试获得该项目,我会:
@Override
public Project loadProject(String projectId) throws DataAccessException {
try(DatabaseSession session = new DatabaseSession()) {
return session.fetch(session.load(Project.class, mandt, projectId), (s, r) -> {
s.fetch(r.getPropertySet());
s.fetch(r.getOwner());
s.fetch(r.getResponsibility());
s.fetch(r.getProjectGuideline());
});
} catch(RuntimeException e) {
throw new DataAccessException(e);
}
}
但在这种情况下,属性集保持为空。它甚至没有初始化。当我初始化它时,它保持为空。我可以通过使用 em.refresh 来修复其他获取,但我已经添加了一个 TODO,因为刷新总是会导致数据库命中。属性实体位于数据库中,我可以通过单独的特定 SELECT 查询找到它。
这个数据库设置的主要要求是我们支持数据库内容的高并发编辑。由于数据库通过原子化提交来修复并发问题,因此我认为我在这里不会出现竞争。
我看到的一个问题是,在添加具有双向关系的实体时,我不会将它们添加到两侧,但是当我稍后再次加载它们时,这个问题不应该再次修复吗(可能不是因为它们被缓存了)?此外,它并没有解决我在直接 OneToMany 关系中遇到的任何其他问题(与此处具有嵌套 OneToMany 的 OneToOne 相比),我仍然需要 em.refresh(...)。如果在服务器环境中,em 是否以无竞争的方式维护实体?
如果您需要更多信息,请告诉我。
编辑:
这个问题似乎与我在这里所做的单元测试的设置有关,内存中的 H2 数据库似乎与 eclipselink 混淆,但是以下注释在生产系统中工作得很好(MsSQL 上的 eclipselink):
Project.java
package pm.data;
...common imports...
import platform.data.DatabaseBindingIds;
import platform.data.MandtId;
import platform.data.PropertySet;
import platform.data.ReadWriteRecord;
import resource.data.Resource;
@Entity
@IdClass(MandtId.class)
public class Project extends ReadWriteRecord {
@Id
@Column(name=DatabaseBindingIds.PROJECT_TENANT)
private String mandt;
@Id
@Column(name=DatabaseBindingIds.PROJECT_ID)
private String entityId;
@OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.ALL) // one to one mappings are directly mapped using the project primary keys
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROJECT_TENANT, referencedColumnName=DatabaseBindingIds.PROPERTYSET_TENANT, insertable=false, updatable=true),
@JoinColumn(name=DatabaseBindingIds.PROJECT_ID, referencedColumnName=DatabaseBindingIds.PROPERTYSET_ID, insertable=false, updatable=true)
} )
private PropertySet propertySet;
@OneToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL) // one to one mappings are directly mapped using the project report primary keys
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROJECTREPORT_TENANT, referencedColumnName=DatabaseBindingIds.INDICATORSET_TENANT, insertable=false, updatable=false),
@JoinColumn(name=DatabaseBindingIds.PROJECTREPORT_ID, referencedColumnName=DatabaseBindingIds.INDICATORSET_ID, insertable=false, updatable=false)
} )
private IndicatorSet indicatorSet; // NOTE: Yes, the updatable are false here and are only true in one set.
...other member variables...
...same as above...
...getters and setters for other member variables...
}
PropertySet.java
package platform.data;
import java.util.ArrayList;
import java.util.List;
...common imports...
@Entity
@IdClass(MandtId.class)
@Cache(isolation=CacheIsolationType.ISOLATED) // Fix turns off EclipseLink cache for PropertySet
public class PropertySet extends ReadWriteRecord {
...same as above...
我接受了克里斯的回答,因为它帮助我理解了发生的问题以及缓存的工作原理。对于 PropertySet,我必须关闭缓存。解决问题的选项列表也非常有帮助。
最佳答案
您提到的问题与 Project->PropertySet 关系有关,这是严格的 OneToOne 映射,并且显示的实体没有显示问题涉及 OneToMany。由于不是双向的,所以和传统的不设置后退指针没有关系,但还是有一定关系的
问题是因为此 OneToOne 映射的外键也是项目 ID 字段,这些字段被映射为可写的基本映射。为了解决多个可写映射异常,您将 Project.propertySet 映射的连接列标记为 insertable=false、updatable=false,本质上是告诉 EclipseLink 该映射是只读的。因此,当您设置或更改关系时,此“更改”将被忽略并且不会合并到缓存中。这会导致您创建的实体在从缓存中读取时该引用始终为空,除非从数据库中刷新/重新加载该实体。这仅影响二级缓存,因此不会显示在创建它的 EntityManager 中,除非清除它。
有几种方法可以解决这个问题,哪种方法最好取决于您的应用程序的使用情况。
禁用共享缓存。这可以针对每个实体或针对特定实体来完成。看eclipseLink faq了解详情。这是最简单的选择会给你类似于 Hibernate 的结果,它不启用默认情况下二级缓存,但不推荐这样做,除非有不使用二级缓存的其他考虑因素,因为它以性能为代价。
更改要使用的 Project 中的基本 ID 映射字段可插入=假,可更新=假。然后您删除从连接列可插入=假,可更新=假,允许OneToOne 映射来控制您的主键。功能上这个不应以任何方式更改您的应用程序。如果你得到同样的基本映射问题, native EclipseLink postClone方法可用于设置引用映射中的字段,或者您的实体 get 方法可以快速检查是否存在 PropertySet并在返回 null 之前使用该值。
使用 JPA 2.0 的派生 ID。 JPA 允许将关系标记为 ID,从而无需为同一值提供这两个基本映射。或者,您可以在关系上使用 @MapsId 来告诉 JPA 该关系控制该值,JPA 将为您设置这些字段。使用 @MapsId 需要使用您的 pk 类作为嵌入式 ID,并且看起来像:
@Entity
public class Project extends ReadWriteRecord {
@EmbeddedId
private MandtId mandtId;
@MapsId("mandtId")
@OneToOne(fetch=FetchType.LAZY, cascade= CascadeType.ALL) // one to one mappings are directly mapped using the project primary keys
@JoinColumns( {
@JoinColumn(name=DatabaseBindingIds.PROJECT_TENANT, referencedColumnName=DatabaseBindingIds.PROPERTYSET_TENANT, insertable=false, updatable=false),
@JoinColumn(name=DatabaseBindingIds.PROJECT_ID, referencedColumnName=DatabaseBindingIds.PROPERTYSET_ID, insertable=false, updatable=false)
} )
private PropertySet propertySet;
关于java - EclipseLink 不使用嵌套的 Lazy OneToMany 关系填充 Lazy OneToOne,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38760611/
所以我试图设置“内容”类的高度,但它似乎不起作用。我对嵌套 DIV 非常陌生,我已经尝试了我在谷歌搜索中发现的修复程序,但似乎没有任何效果。帮助?
好的,所以我一直在四处寻找,但找不到这个问题的答案。但是,我需要将一个 View 嵌套在另一个 View 中。 我有一个 $layout 正在使用我拥有的 default.layout Blade 文
好的,所以我一直在四处寻找,但找不到这个问题的答案。但是,我需要将一个 View 嵌套在另一个 View 中。 我有一个 $layout 正在使用我拥有的 default.layout Blade 文
基本上,我的问题很简单,但它需要知道 Struts 1.1 并且还活着的人。 我尝试构建的伪代码看起来像这样: IF element.method1 = true THEN IF element
我正在尝试将 Excel 嵌套 IF 语句转换为代码语言,但我不确定我是否正确执行此操作,希望能得到一些帮助 这是Excel语句: =IF(D3="Feather",IF(OR(I3>1000,R3=
如果我们创建两个或三个评论并对其进行多次回复,则“有用”链接在单击时会导致问题,它会对具有相同编号的索引执行 ng-click 操作,从而显示具有相同索引的所有文本。如何解决此嵌套问题,以便在单击链接
我在项目中使用Scala,想与Stripe集成,但它只提供Java API。例如,要创建 session ,我使用: val params = new util.HashMap[String, Any
以下代码有一个 Div,其中连续包含四个较小的 Div。四个 Div 中的每一个还包含一个较小的 Div,但此 Div 未显示。我尝试了各种显示和位置组合,看看 div 是否会出现。 classGoa
我在这里有一个问题,循环是: for (i=0; i < n; ++i) for (j = 3; j < n; ++j) { ...
我正在尝试编写代码来显示具有奇数宽度的形状。形状完成后,将其放置在外部形状内。用户将能够输入用于形状的字符和行数。我希望生成一个形状,并通过 for 循环生成一个外部形状。 ***** .
$(".globalTabs").each(function(){ var $globalTabs = $(this); var parent = $globalTabs.parent
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 9 年前。 Improve th
所以我在这个问题上遇到了一些麻烦,因为变量 i。我只是不确定在第二个 while 循环中如何处理它。对于我的外循环,我知道它将运行 log_4(n^2) 次迭代。对于内部 while 循环,我计算的迭
我似乎找不到在枚举上应用多个 if/then 逻辑的工作方式。 anyOf 不应用条件逻辑,而是表示如果其中任何一个匹配则很好。 allOf 再次不应用条件逻辑,而是测试属性/必填字段的超集。 这是一
如何访问 ReaderT 的内部 monad。 在我的例子中,我有类型: newtype VCSSetupAction a = VCSSetupAction (ReaderT (Maybe VCSCo
这个问题在这里已经有了答案: Add leading zeroes/0's to existing Excel values to certain length (7 个回答) 7年前关闭。 我正在寻
我已经绑定(bind)了很多 AND/OR 函数的组合并且没有运气。 这是我需要创建的: 在 B 列中,我有公司 ID,范围从两个数字字符到六个数字字符。 我需要在 B 列中的每个公司 ID 之前的每
我是 VBA 新手,在尝试编写的宏中使用 If 语句时遇到了一些困难。每个月我都会收到一份 Excel 报告,其中列出了我们公司的哪些员工执行了某些任务。我正在编写的宏旨在将每个员工的数据复制并粘贴到
如果在 B 列中找到单元格 A1 中的值,则使用文本 321 填充除非在 C 列中找到单元格 A1 中的值,在这种情况下填充文本 121反而。如果单元格 A1 的内容不在 B 列或 C 列中,则使用
我有几十万个地址。其中一些在整数之后有粒子。如 4356 A Horse Avenue , 其他格式正常4358 Horse Avenue .有些有“A”,有些有“B”。我正在尝试删除整数和粒子之间的
我是一名优秀的程序员,十分优秀!