I would like to do the following:
Inserting the CityHistory into the database using JPA.
The first time there is no data, so a new city will be inserted. (IT WORKS FINE)
the (IDENTIFICATION) within the city table is a unique field.
What I want to achieve is when I am inserting the same city again is to reuse the existing field instead of trying to create a new one (identification will be like a city's unique name).
So how can I do that using JPA or Hibernate?
#Entity
public class CityHistory extends History implements Serializable {
#Id
#Column(name = "KEY_CITY_HISTORY", nullable = false, precision = 19)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "CITY_ID", nullable = false, foreignKey = #ForeignKey(name = "FK_CITY_ID"))
private City cityId;
#Column(name = "CITY_NAME", nullable = false)
private String cityName;
}
#Entity
public class City implements Serializable {
#Id
#Column(name = "KEY_CITY", nullable = false, precision = 19)
private Long id;
#Column(name = "IDENTIFICATION", nullable = false, unique = true)
private String identification;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MUNICIPALITY_ID", foreignKey = #ForeignKey(name = "FK_MUNICIPALITY_ID"))
private Municipality municipalityId;
}
UPDATE
Here is how I am writing the data to the database,
It's a Spring Batch itemWriter
#Component
public class InfoItemWriter implements ItemWriter<Object> {
#Autowired
private CityHistoryRepository cityHistoryRepository;
#Override
public void write(List<? extends Object> items) throws Exception {
if (items.size() > 0 && items.get(0) instanceof CityHistory) {
cityHistoryRepository.saveAll((List<? extends CityHistory>) items);
}
}
}
First of all thanks to all who tried to help!
Reading the resources that #Benjamin Maurer provided:
I don't think you want the cascade on the ManyToOne side, see One-To-Many
The most common Parent – Child association consists of a one-to-many and a many-to-one relationship, where the cascade being useful for the one-to-many side only
As the relation I have is ManyToOne it was really not useful to use the cascade and doesn't serve my need.
I used a different approache to reach the goal. I have created a service where it validates the existence of a city, then adds a new city if it does not exist.
#Service
public class CityHistoryServiceImpl implements CityHistoryService {
#Autowired
CityRepository cityRepository;
#Autowired
CityHistoryRepository cityHistoryRepository;
#Override
public Optional<CityHistory> addCityHistory(City city, String cityName, ..) {
if (city != null && cityName != null) {
City city1 = addCityIfNotExist(city);
CityHistory cityHistory = new CityHistory();
cityHistory.setCityId(city1);
cityHistory.setCityName(cityName);
cityHistoryRepository.save(cityHistory);
return Optional.of(cityHistory);
}
return Optional.empty();
} ....
private City addCityIfNotExist(City city) {
City city1 = cityRepository.findFirstByBagId(city.getBagId());
if (city1 == null) {
city1 = cityRepository.save(city);
}
return city1;
}
}
Hibernate will use the #Id property of City to determine if it is new or not. When it is null, Hibernate couldn't possibly know that a similar entry already exists.
So you need to perform a query to find each city first:
for (var history : histories) {
var cities = em.createQuery("select city from City city where city.identification = ?1", City.class)
.setParameter(1, history.getCityId().getIdentification())
.getResultList();
if (!cities.isEmpty()) {
history.setCityId(cities.get(0));
}
em.persist(history);
}
If you use Hibernate and City.identification is unique and always non-null, you can use it as a NaturalID:
In City:
#NaturalId
private String identification;
Then:
for (var history : histories) {
var city = em.unwrap(Session.class)
.byNaturalId(City.class)
.using("identification", history.getCityId().getIdentification())
.getReference();
if (city != null) {
history.setCityId(city);
}
em.persist(history);
}
But if you do have City.id set, i.e., not null, you can use EntityManager.merge to get a managed entity:
for (var history : histories) {
City city = history.getCityId();
if (city.getId() != null) {
city = em.merge(city);
history.setCityId(city);
}
em.persist(history);
}
One more remark: We are not in the relational domain, but we are mapping object graphs. So calling your fields cityId and municipalityId is arguably wrong - even the type says so: City cityId.
They are not just plain identifiers, but full fledged objects: City city.
Related
So my second post. This time i worked on a passion project of mine, which turned out to be far more complicated than I expected and again I need some help.
I have two enitites: Gamestate and User.
Users are supposed to be able to join multiple Games(/gamestates). Games(/gamestates) are supposed to have muliple people join them. So therefore it is represented as a N:M Relation.
Depending on who joins and when they join they are supposed to have different roles, giving them different rights in the app. Which means I needed an N:M Relation with custom fields and therefore I had to model the relation table myself. That's as far as I have come.
Abstract Model:
#EqualsAndHashCode
#Getter
#Setter
#ToString
public abstract class AbstractModel {
#Id
#GeneratedValue
protected Long id;
#NotNull
protected String identifier;
}
User
#Getter
#Setter
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractModel{
private String nickName;
private UserRole role;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
private LoginInformation loginInformation;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "gameState")
private List<UserGameState> userGameStates = new ArrayList<>();
//DTO Constructor
public User(UserDTO userDTO){
this.identifier = Optional.ofNullable(userDTO.getIdentifier())
.orElse(UUID.randomUUID().toString());
this.nickName = userDTO.getNickName() == null ? "": userDTO.getNickName();
this.role = UserRole.valueOf(userDTO.getRole());
this.loginInformation = null;
if(userDTO.getLoginInformation() != null) {
setLoginInformation(new LoginInformation(userDTO.getLoginInformation()));
} else {
setLoginInformation(new LoginInformation());
}
(userDTO.getUserGameStates() == null ? new ArrayList<GameStateDTO>() : userDTO.getUserGameStates())
.stream()
.map(x -> new UserGameState((UserGameStateDTO) x))
.forEach(this::addUserGameState);
}
GameState
#Getter
#Setter
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
public class GameState extends AbstractModel{
private String name;
private String description;
private String image;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "user")
private List<UserGameState> userGameStates = new ArrayList<>();
//DTO Constructor
public GameState(GameStateDTO gameStateDTO){
this.identifier = Optional.ofNullable(gameStateDTO.getIdentifier())
.orElse(UUID.randomUUID().toString());
this.name = gameStateDTO.getName() == null ? "": gameStateDTO.getName();
this.description = gameStateDTO.getDescription() == null ? "": gameStateDTO.getDescription();
this.image = gameStateDTO.getImage() == null ? "": gameStateDTO.getImage();
(gameStateDTO.getUserGameStates() == null ? new ArrayList<UserDTO>() : gameStateDTO.getUserGameStates())
.stream()
.map(x -> new UserGameState((UserGameStateDTO) x))
.forEach(this::addUserGameState);
}
//----------------------1:1 Relationship Methods----------------------
//----------------------1:N Relationship Methods----------------------
public void addUserGameState(UserGameState userGameState) {
if (userGameStates.contains(userGameState)) {
return;
}
userGameStates.add(userGameState);
userGameState.setGameState(this);
}
public void removeUserGameState(UserGameState userGameState) {
if (!userGameStates.contains(userGameState)) {
return;
}
userGameState.setGameState(null);
userGameStates.remove(userGameState);
}
//----------------------N:1 Relationship Methods----------------------
//----------------------N:M Relationship Methods----------------------
}
UserGameSatet (Custom N:M Table)
#Getter
#Setter
#Entity
#Builder
#ToString
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class UserGameState{
#EmbeddedId
private User_GameState_PK id;
#ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
#MapsId("user_id")
#JoinColumn(name = "USER_ID", insertable = false, updatable = false)
private User user;
#ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
#MapsId("gameState_id")
#JoinColumn(name = "GAMESTATE_ID", insertable = false, updatable = false)
private GameState gameState;
//add Role later
public UserGameState(User u, GameState gs) {
// create primary key
this.id = new User_GameState_PK(u.getId(), gs.getId());
// initialize attributes
setUser(u);
setGameState(gs);
}
public UserGameState(UserGameStateDTO userGameStateDTO){
//this.id =
this.user = null;
this.gameState = null;
}
//----------------------1:1 Relationship Methods----------------------
//----------------------1:N Relationship Methods----------------------
//----------------------N:1 Relationship Methods----------------------
public void setUser(User user) {
if (Objects.equals(this.user, user)) {
return;
}
User oldUser = this.user;
this.user = user;
if (oldUser != null) {
oldUser.removeUserGameState(this);
}
if (user != null) {
user.addUserGameState(this);
}
}
public void setGameState(GameState gameState) {
if (Objects.equals(this.gameState, gameState)) {
return;
}
GameState oldGameState = this.gameState;
this.gameState = gameState;
if (oldGameState != null) {
oldGameState.removeUserGameState(this);
}
if (oldGameState != null) {
oldGameState.addUserGameState(this);
}
}
//----------------------N:M Relationship Methods----------------------
}
User_GameState_PK (Combined Key)
#Embeddable
#Builder
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
public class User_GameState_PK implements Serializable {
#Column(name = "USER_ID")
private Long user_id;
#Column(name = "GAMESTATE_ID")
private Long gameState_id;
public User_GameState_PK(long user_id, long gameState_id){
this.user_id = user_id;
this.gameState_id = gameState_id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
User_GameState_PK that = (User_GameState_PK) o;
return Objects.equals(user_id, that.user_id) &&
Objects.equals(gameState_id, that.gameState_id);
}
#Override
public int hashCode() {
return Objects.hash(user_id, gameState_id);
}
}
The method saving the Connection in my Service
(both GameState and User are already instantiated, and the method gets the identifier of both objects, retrieving them from the database and adding the relation between them.)
public Optional<GameStateDTO> addUserToGameState(String identifierGS, String identifierU) {
GameState gameState = gameStateRepo.findByIdentifier(identifierGS)
.orElseThrow(() -> new IllegalArgumentException("GameState ID has no according GameState."));
User user = userRepo.findByIdentifier(identifierU)
.orElseThrow(() -> new IllegalArgumentException("User ID has no according User."));
//Custom N:M Connection Part
UserGameState connection = new UserGameState(user, gameState);
userGameStateRepo.save(connection);
return Optional.of(gameState)
.map(m -> convertModelIntoDTO(m));
}
I managed to set the N:M table up, together with its combined key. I tested it with simple CRUD Routes, and they worked.
Next I tried to set up some routes so that people could actually join a game(/gamestate) at which point it throws the following exception upon saving.
javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.Astralis.backend.model.UserGameState#User_GameState_PK(user_id=1, gameState_id=7)]
After reading through some posts on stackoverflow I tried out changing the Cascadetype to .MERGE, which resulted in this exception.
javax.persistence.EntityNotFoundException: ...
Really I am lost here, it feels like if I use .PERSIST, Hibernate complaines that it copies itself while saving the Relation. While if I change it to .MERGE, it complaines that the value isn't already present in the first place.
I am more than thankfull for any breadcrumb bringing me closer to a solution, as this turned out to be a gigantic roadblock for the project, and I have tried out everything that I can think of.
So after a few more days of searching I managed to solve it.
For this I first remade a guide's project in with the data structure from the guide and the service/controller structure of my project. Testing if it would work, and as it did I just started comparing the models with each other and tried all different possibilities out, to find out what is actually causing the issues.
The used guide is this one: https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/
I had six Copy&Paste (kinda) mistakes that caused Hibernate to falsely associate table columns with each other. These were:
in User:
...
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(
cascade = {CascadeType.PERSIST},
fetch = FetchType.LAZY,
mappedBy = "user",// changed from gameState to user
orphanRemoval = true
)
private List<UserGameState> userGameStates = new ArrayList<>();
...
in GameState the reverse:
...
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(cascade = {CascadeType.PERSIST},
fetch = FetchType.LAZY,
mappedBy = "gameState",// changed from user to gameState
orphanRemoval = true)
private List<UserGameState> userGameStates = new ArrayList<>();
...
3&4. The JoinColumn Annotations were unnecessary, seemingly I combiend multiple guides into one project. This caused then even more issues:
...
#ManyToOne(
cascade = {CascadeType.PERSIST},
fetch = FetchType.LAZY)
#MapsId("user_id")
//#JoinColumn(name = "USER_ID", insertable = false, updatable = false) //this one removed
private User user;
#ManyToOne(
cascade = {CascadeType.PERSIST},
fetch = FetchType.LAZY)
#MapsId("gameState_id")
//#JoinColumn(name = "GAMESTATE_ID", insertable = false, updatable = false) //this one removed
private GameState gameState;
...
5&6. Two minor copy&paste mistakes, in the "continuity keeper" methods in UserGameState:
...
public void setGameState(GameState gameState) {
if (Objects.equals(this.gameState, gameState)) {
return;
}
GameState oldGameState = this.gameState;
this.gameState = gameState;
if (oldGameState != null) {
oldGameState.removeUserGameState(this);
}
//I copied the previous if block, and replaced the remove... with add...
//But I didn't change the oldGameState to gameState.
//This didn't throw any errors, and actually it still created the relations properly, but I am pretty sure it would cause issues further down the line.
if (gameState != null) {
gameState.addUserGameState(this);
}
}
...
So how does this work now:
As before, when the route with the Identifiers for the connected GameState and User is called, the service "addUserToGameState" is called, getting the models with the given Identifiers.
...
public Optional<GameStateDTO> addUserToGameState(String identifierGS, String identifierU) {
GameState gameState = gameStateRepo.findByIdentifier(identifierGS)
.orElseThrow(() -> new IllegalArgumentException("GameState ID has no according GameState."));
User user = userRepo.findByIdentifier(identifierU)
.orElseThrow(() -> new IllegalArgumentException("User ID has no according User."));
//Custom N:M Connection Part
UserGameState connection = new UserGameState(user, gameState);
return Optional.of(gameState)
.map(m -> convertModelIntoDTO(m));
}
...
After that the UserGameState cosntructer is called, which sets and creates the combined key and calls the setter methods for the related User/GameState fields.
...
public UserGameState(User u, GameState gs) {
// create primary key
this.id = new User_GameState_PK(u.getId(), gs.getId());
// initialize attributes
setUser(u);
setGameState(gs);
}
...
I wrote the setters in a way, that they at the same time, check the added models for relationship consistency issues, and adjust their fields according to if they are newly edited or replaced.
...
public void setUser(User user) {
if (Objects.equals(this.user, user)) {
return;
}
User oldUser = this.user;
this.user = user;
if (oldUser != null) {
oldUser.removeUserGameState(this);
}
if (user != null) {
user.addUserGameState(this);
}
}
public void setGameState(GameState gameState) {
if (Objects.equals(this.gameState, gameState)) {
return;
}
GameState oldGameState = this.gameState;
this.gameState = gameState;
if (oldGameState != null) {
oldGameState.removeUserGameState(this);
}
if (gameState != null) {//copy paste error
gameState.addUserGameState(this);
}
}
...
The simplified question would be:
Having in a “A” entity, a collection of “B” entities (from a OneToMany relationship) and using CriteriaBuilder, how to get the elements from "A" that have in this collection at least one element "B" that meets a condition? (for example B has a property equal to 10, or as another example B points to another entity whose property is equal to 10).
To put in context:
In my program I have the following entities
#Entity
#SequenceGenerator(name = "sequence", sequenceName = "S_EXPEDIENT")
public class Expedient {
private Long idExpedient
private Integer numberExpedient
private Integer yearExpedient
#ManyToOne
#JoinColumn(name = "id_chamber")
private Chamber chamber
private Integer status
#OneToMany(cascade = CascadeType.ALL, mappedBy = "expedient")
private List<HistoricalAssignment> assignments = new ArrayList<HistoricalAssignment>();
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_HISTORICAL_ASSIGNMENT ")
#Entity
public class HistoricalAssignment {
private Long idHistoricalAssignment
#ManyToOne
#JoinColumn(name = "id_expedient")
private Expedient expedient;
#ManyToOne
#JoinColumn(name = "id_office")
private Office office;
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_OFFICE")
#Entity
public class Office extends {
private Long idOffice
#ManyToOne
#JoinColumn(name = "id_chamber")
private Chamber chamber
//more fields
}
#SequenceGenerator(name = "sequence", sequenceName = "S_CHAMBER")
#Entity
public class Chamber{
private Long id_chamber
private String description;;
//more fields
}
I am also using Spring Data Specifications to perform searches with filters.
The problem I have is when trying to get all the "Expedients" that were assigned at least once to a "Chamber", since I would have to look for those who have in the collection "assignments" at least one "Office" that belongs to a "Chamber."
I edit by adding more code. Keep in mind that the original code has several levels of inheritance (here simplified) and also the original model is in Spanish:
public class ExpedientSpecification implements Specification<Expedient> {
private final Integer STATUS_DELETED = -1;
protected ExpedientFilter criteria;
protected List<Predicate> predicates = new ArrayList<Predicate>();
public ExpedientSpecification(ExpedientFilter criteria) {
this.criteria=criteria;
}
public Predicate toPredicate(Root<Expedient> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//variable filters
if(criteria.getId() != null){
predicates.add(cb.equal(root.get("id"), criteria.getId()));
}
if(criteria.getNumberExpedient() != null){
predicates.add(criteriaBuilder.equal(root.get("numberExpedient"), criteria.getNumberExpedient()));
}
if(criteria.getYearExpedient() != null){
predicates.add(criteriaBuilder.equal(root.get("yearExpedient"), criteria.getYearExpedient()));
}
//Fixed Filters
predicates.add(criteriaBuilder.notEqual(root.get("status"), STATUS_DELETED));
// This is where I need to add the filter. I need to get only those in the "assignments" List have at least one
// "Office" that belongs to a "Chamber"
Predicate returnPredicates = cb.and(predicates.toArray(new Predicate[this.predicates.size()]));
predicates.clear();
return returnPredicates;
}
}
How are you doing the criteria? Can you post the code?
Anyway you can join oneToMany normally with a criteria alias.
Example: criteria.createAlias("assignments","assignments"); and then add a restriction on the atributte from assignments that you wish to filter.
Hibernate 4.3.11
I have an issue saving the following object graph in hibernate. The Employer is being saved using the merge() method.
Employer
|_ List<EmployerProducts> employerProductsList;
|_ List<EmployerProductsPlan> employerProductsPlan;
The Employer & EmployerProducts have a auto generated pk. The EmployerProductsPlan is a composite key consisting of the EmployerProducts id and a String with the plan code.
The error occurs when there is a transient object in the EmployerProducts list that cascades to List<EmployerProductsPlan>. The 1st error that I encountered which I have been trying to get past was an internal hibernate NPE. This post here perfectly describes the issue that I am having which causes the null pointer Hibernate NullPointer on INSERTED id when persisting three levels using #Embeddable and cascade
The OP left a comment specifying what they did to resolve, but I end up with a different error when changing to the suggested mapping. After changing the mapping, I am now getting
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.webexchange.model.EmployerProductsPlan#com.webexchange.model.EmployerProductsPlanId#c733f9bd]
Due to other library dependencies, I cannot upgrade above 4.3.x at this time. This project is using spring-boot-starter-data-jpa 1.3.3. No other work is being performed on the session other than calling merge() and passing the employer object.
Below is the mappings for each class:
Employer
#Entity
#Table(name = "employer")
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerNo"})
public class Employer implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "EMPLOYER_NO", unique = true, nullable = false)
private Long employerNo;
.....
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employer", orphanRemoval = true)
private List<EmployerProducts> employerProductsList = new ArrayList<>(0);
}
EmployerProducts
#Entity
#Table(name = "employer_products")
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerProductsNo"})
public class EmployerProducts implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "employer_products_no", unique = true, nullable = false)
private Long employerProductsNo;
#ManyToOne
#JoinColumn(name = "employer_no", nullable = false)
private Employer employer;
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employerProducts", orphanRemoval = true)
private List<EmployerProductsPlan> employerProductsPlanList = new ArrayList<>(0);
}
EmployerProductsPlan
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"id"})
#Entity
#Table(name="employer_products_plan")
public class EmployerProductsPlan implements Serializable {
#EmbeddedId
#AttributeOverrides({ #AttributeOverride(name = "plan", column = #Column(name = "epp_plan", nullable = false)),
#AttributeOverride(name = "employerProductsNo", column = #Column(name = "employer_products_no", nullable = false)) })
private EmployerProductsPlanId id;
#ManyToOne
#JoinColumn(name = "employer_products_no")
#MapsId("employerProductsNo")
private EmployerProducts employerProducts;
}
I am populating the employerProducts above with the same instance of the EmployerProducts object that is being saved. It is transient and has no id populated as it does not existing in the db yet.
EmployerProductsPlanId
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"plan", "employerProductsNo"})
#Embeddable
public class EmployerProductsPlanId implements Serializable {
private String plan;
private Long employerProductsNo;
// This was my previous mapping that was causing the internal NPE in hibernate
/* #ManyToOne
#JoinColumn(name = "employer_products_no")
private EmployerProducts employerProducts;*/
}
UPDATE:
Showing struts controller and dao. The Employer object is never loaded from the db prior to the save. Struts is creating this entire object graph from the Http request parameters.
Struts 2.5 controller
#lombok.Getter
#lombok.Setter
public class EditEmployers extends ActionHelper implements Preparable {
#Autowired
#lombok.Getter(AccessLevel.NONE)
#lombok.Setter(AccessLevel.NONE)
private IEmployerDao employerDao;
private Employer entity;
....
public String save() {
beforeSave();
boolean newRecord = getEntity().getEmployerNo() == null || getEntity().getEmployerNo() == 0;
Employer savedEmployer = newRecord ?
employerDao.create(getEntity()) :
employerDao.update(getEntity());
setEntity(savedEmployer);
return "success";
}
private void beforeSave() {
Employer emp = getEntity();
// associate this employer record with any products attached
for (EmployerProducts employerProduct : emp.getEmployerProductsList()) {
employerProduct.setEmployer(emp);
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.setEmployerProducts(employerProduct));
}
// check to see if branding needs to be NULL. It will create the object from the select parameter with no id
// if a branding record has not been selected
if (emp.getBranding() != null && emp.getBranding().getBrandingNo() == null) {
emp.setBranding(null);
}
}
}
Employer DAO
#Repository
#Transactional
#Service
#Log4j
public class EmployerDao extends WebexchangeBaseDao implements IEmployerDao {
private Criteria criteria() {
return getCurrentSession().createCriteria(Employer.class);
}
#Override
#Transactional(readOnly = true)
public Employer read(Serializable id) {
return (Employer)getCurrentSession().load(Employer.class, id);
}
#Override
public Employer create(Employer employer) {
getCurrentSession().persist(employer);
return employer;
}
#Override
public Employer update(Employer employer) {
getCurrentSession().merge(employer);
return employer;
}
}
As of right now, my solution is to loop through the EmployerProducts and check for new records. I called a persist on the new ones before calling the merge() on the parent Employer. I also moved the logic I had associating all the keys into the dao instead of having it in my Struts action. Below is what my update() method in the Employer DAO now looks like
public Employer update(Employer employer) {
// associate this employer record with any products attached
for (EmployerProducts employerProduct : employer.getEmployerProductsList()) {
employerProduct.setEmployer(employer);
if (employerProduct.getEmployerProductsNo() == null) {
// The cascade down to employerProductsPlanList has issues getting the employerProductsNo
// automatically if the employerProduct does not exists yet. Persist the new employer product
// before we try to insert the new composite key in the plan
// https://stackoverflow.com/questions/54517061/hibernate-4-3-cascade-merge-through-multiple-lists-with-embeded-id
List<EmployerProductsPlan> plansToBeSaved = employerProduct.getEmployerProductsPlanList();
employerProduct.setEmployerProductsPlanList(new ArrayList<>());
getCurrentSession().persist(employerProduct);
// add the plans back in
employerProduct.setEmployerProductsPlanList(plansToBeSaved);
}
// associate the plan with the employer product
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.getId().setEmployerProductsNo(employerProduct.getEmployerProductsNo())
);
}
return (Employer)getCurrentSession().merge(employer);
}
I am using envers in my project to audit data.
Now I want to access changed data with audit query.
My pojo class for table is below
#Entity
#Audited(withModifiedFlag=true)
#Table(name = "INSTRUMENT", uniqueConstraints = #UniqueConstraint(columnNames = "INSTRUMENT_NAME"))
public class Instrument implements java.io.Serializable {
private long instrumentId;
private String instrumentName;
private WorkflowState workflowState;
#Id
#Column(name = "INSTRUMENT_ID", unique = true, nullable = false, precision = 22, scale = 0)
public long getInstrumentId() {
return this.instrumentId;
}
public void setInstrumentId(long instrumentId) {
this.instrumentId = instrumentId;
}
#Column(name = "INSTRUMENT_NAME", unique = true, nullable = false, length = 50)
public String getInstrumentName() {
return this.instrumentName;
}
public void setInstrumentName(String instrumentName) {
this.instrumentName = instrumentName;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS", nullable = false)
public WorkflowState getWorkflowState() {
return this.workflowState;
}
public void setWorkflowState(WorkflowState workflowState) {
this.workflowState = workflowState;
}
}
Now I tried to access this with audit query as
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Instrument.class, false, true)
.add(AuditEntity.property("status").hasChanged());
List list= query.getResultList();
So at the time of accessing getResultList() , Its throwing Exception as follows
SqlExceptionHelper: Fail to convert to internal representation
I figured it out, this is because in my db Instrument.status column is as String data Type. While here I am using Join.
So please tell me how to write query to resolve this problem
PROBLEM is How to write Audit Query if my table has foreign key (class property have join dependency).
Join table WorkflowState discription is as follows
public class WorkflowState implements java.io.Serializable {
private BigDecimal stateId;
private Workflow workflow;
private String stateName;
//getters and setters
And it has a join column too i.e "workflow" .
Use workflowState rather than status. The API is based on you specifying the property name and not the column name.
Technologies: JPA, eclipseLink, postgreSQL.
I'm looking for a nice way to solve a problem I have:
My datamodel contains an Employee-entity, that has a OneToMany relationship with the entity Task, that has a startDate and an endDate (and some more attributes).
When I am loading an employee, I always need the tasks as well, so I have set the fetchtype to eager.
The problem is, the longer the application is used, an employee has more and more tasks referenced, but most of them are (far) in the past.
So to prevent the application performance from suffering, I am looking for a JPA-annotation-based way to define conditions for the fetchtype, in this case, I only want to load the tasks for an employee, that have an endDate after last midnight.
I have found a solution with the annotation #PostLoad (see code below), but I was wondering if there is a possibility to do the same thing with an annotation on the field.
What is, in your opinion, the nicest way to do this?
Thanks for the help!
#Entity
#Table(name = "tbl_employee")
public class Employee implements Serializable {
//... (attributes)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employee")
private List<Task> tasks;
#PostLoad
public void loadTasks() {
tasks = new TaskDao().getTasksByEmployeeByToday(this);
}
//... (getters, setters)
}
#Entity
#Table(name = "tbl_task")
public class Task implements Serializable {
//... (attributes)
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "fi_employee", referencedColumnName = "id_employee")
private Employee employee;
#Column(name = "start_date", nullable = true)
private Timestamp startDate;
#Column(name = "end_date", nullable = true)
private Timestamp endDate;
//... (getters, setters)
}
public class TaskDao extends GenericDaoCRUD<Task> {
public List<Task> getTasksByEmployeeByDate(Employee employee, Timestamp date) {
Timestamp lastMidnight = Utils.getLastMidnight(date);
String statement = "SELECT t FROM Task t WHERE t.employee = :employee and t.endDate > :enddate";
TypedQuery<Task> query = getEntityManager().createQuery(statement, Task.class);
query.setParameter("employee", employee);
query.setParameter("enddate", lastMidnight);
try {
return query.getResultList();
} catch (NoResultException e) {
LOG.info("No tasks found for employee=" + employee + "; and date>"
+ lastMidnight);
}
return null;
}
public List<Task> getTasksByEmployeeByToday(Employee employee) {
return getTasksByEmployeeByDate(employee, new Timestamp(System.currentTimeMillis()));
}
Instead of your proposal, I'd like take another thread to move the historic data into another table to improve the performance.
and add another view to union the main table and history table in case you are retriveing some data for report or something.