JPA @Transient fields being cleared / null on Callbacks / Listener

I had the that problem and I have decided the following …

Worked and Tested on WindFly AS 38 with Jakarta EE 10
When entity persist/merge/remove callbacks before - save @Transient fields saved on Singleton class (use current module’s general Helper class)
When entity persist/merge/remove callbacks during/after - use @Transient fields from Singleton class (use current module’s general Helper class)
When entity persist/merge/remove operation finished - restore @Transient fields to entity from Singleton class (use current module’s general Helper class)

// Entity class

@Entity
@EntityListeners(UNIFooEntityListener.class)
@Table(name = “foo_entity”)
public class UNIFooEntity extends BaseEntity {

@Column(length = 128, nullable = false)
private String name;

@Column(length = 32)
private String code;

@Transient
private String optionStr;

@Transient
private Object optionObj;

public UNIFooEntity() {
	super();
}

/** utils **/

public void restoreTransients() {
	this.setOptionStr(UNI.getEntityState().getOptionStr(this));
	this.setOptionObj(UNI.getEntityState().getOptionObj(this));
}

}

// Entity CallBacks like Listener

public class UNIFooEntityListener {

@PrePersist
public void prePersist(FooEntity entity) {
	entity.restoreTransients();
	UNI.getLog().debug("--- Pre Persist ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

@PostPersist
public void posPersist(FooEntity entity) {
	UNI.getLog().debug("--- Post Persist ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

@PreUpdate
public void preUpdate(FooEntity entity) {
	entity.restoreTransients();
	UNI.getLog().debug("--- Pre Update ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

@PostUpdate
public void posUpdate(FooEntity entity) {
	UNI.getLog().debug("--- Post Update ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

@PreRemove
public void preRemove(FooEntity entity) {
	entity.restoreTransients();
	UNI.getLog().debug("--- Pre Remove ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

@PostRemove
public void posRemove(FooEntity entity) {
	UNI.getLog().debug("--- Post Remove ---");
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
	UNI.getLog().debug(entity.getUniqID() + " optionStr : " + entity.getOptionStr());
}

}

// Entity Service Implementation class like EntityRepository

@TransactionManagement(TransactionManagementType.BEAN)
@Stateless(name = EntityService.JNDI)
public class UNIEntityServiceImpl extends BaseEntityService {

@PersistenceUnit(unitName = "PERSISTENCE.UNIT.NAME")
private EntityManagerFactory EMF;

@Resource
private UserTransaction userTXN;

public FooEntity saveFoo(FooEntity entity) throws EntityException, EntityDuplicationException {
	EntityManager em = this.EMF.createEntityManager();
	try {
		super.doEntityStateBegin(entity, this.userTXN, em);
		if (entity.saveTypeIsPersis()) {
			em.persist(entity);
		} else if (entity.saveTypeIsMerge()) {
			entity = em.merge(entity);
		} else if (entity.saveTypeIsDelete()) {
			em.remove(em.getReference(FooEntity.class, entity.getId()));
		} else if (entity.saveTypeIsReference()) {
			em.refresh(entity);
		}
		super.doEntityState(entity, this.userTXN);
	} catch (Exception ex) {
		super.doEntityStateError(entity, this.userTXN, ex);
	} finally {
		super.doEntityStateDone(entity, em);
	}
	return entity;
}

}

// Base Entity Mapped super class

@MappedSuperclass
public class BaseEntity {

@Transient
private String saveType;

public boolean saveTypeIsPersis() {
	return saveType.equals("PERSIS");
}

public boolean saveTypeIsMerge() {
	return saveType.equals("MERGE");
}

public boolean saveTypeIsDelete() {
	return saveType.equals("DELETE");
}

public boolean saveTypeIsReference() {
	return saveType.equals("REFERENCE");
}

}

// Base Entity Service

public abstract class BaseEntityService {

public void doEntityStateBegin(BaseEntity entity, UserTransaction userTXN, EntityManager em) throws NotSupportedException, SystemException {
	UNI.getEntityState().doEntityBegin(entity);
	userTXN.begin();
	em.joinTransaction();
}

public void doEntityState(BaseEntity entity, UserTransaction userTXN) throws SecurityException, IllegalStateException, RollbackException, HeuristicMixedException, HeuristicRollbackException, SystemException {
	UNI.getEntityState().doEntity(entity);
	userTXN.commit();
}

public void doEntityStateDone(BaseEntity entity, EntityManager em) {
	em.close();
	UNI.getEntityState().doEntityDone(entity);
}

}

// Entity State Manager (User Class)

@Startup
@Singleton(name = “EntityStateManager”)
public class EntityStateManager {

private static ConcurrentHashMap<String, String> ENTITY_OPTION_STRS = new ConcurrentHashMap<String, String>();

private static ConcurrentHashMap<String, Object> ENTITY_OPTION_OBJS = new ConcurrentHashMap<String, Object>();

@PostConstruct
public void doInitialize() {
	ENTITY_OPTION_STRS = new ConcurrentHashMap<String, String>();
	ENTITY_OPTION_OBJS = new ConcurrentHashMap<String, Object>();
}

public void doEntityBegin(BaseEntity entity) {
	if (entity != null) {
		this.getEntityOptionStrs().put(entity.UniqID(), entity.getOptionStr());
		this.getEntityOptionObjs().put(entity.UniqID(), entity.getOptionObj());
	}
}

public void doEntity(BaseEntity entity) {
	try {
		entity.setOptionStr(this.getEntityOptionStrs().get(entity.getUniqID()));
	} catch (Exception ex) {
		UNISustem.getLogger().fatal("[entity.state][" + entity.getUniqID() + "]", ex);
	}
}

public void doEntityDone(BaseEntity entity) {
	if (entity != null) {
		this.getEntityOptionStrs().remove(entity.getUniqID());
		this.getEntityOptionObjs().remove(entity.getUniqID());
	}
}

/** getter.Manage **/

public String getOptionStr() {
	return this.getEntityOptionStrs().get(entity.getUniqID());
}

public Object getOptionObj() {
	return this.getEntityOptionObjs().get(entity.getUniqID());
}

/** getter **/

public Map<String, String> getEntityOptionStrs() {
	return ENTITY_OPTION_STRS;
}

public Map<String, String> getEntityOptionObjs() {
	return ENTITY_OPTION_OBJS;
}

}

// Project Domain / Module’s General Helper

public class UNI {

@EJB(beanName = CMNSustemEntityService.JNDI)
private static UNIEntityService entityService;

@EJB(beanName = "EntityStateManager")
private static EntityStateManager entityState;

static {
	// May be Initialize entityService or stateManager
}

/** getter **/

public static UNIEntityService getEntity() {
	return entityService;
}

public static EntityStateManager getEntityState() {
	return entityState;
}

public static Logger getLog() {
	return Logger.getLogger("UNI");
}

}

I’m sorry, but you will have to come up with a more concise and targeted question. Just posting code here without a lot of context makes it hard for anyone to understand what you want.

What I can tell you is that on merge, the passed entity might be replaced by another object, so transient state of the previous object will not be transferred to the new entity object.