detached entity passed to persist when Composite key used with entity - java

i'm getting error like
org.springframework.dao.InvalidDataAccessApiUsageException: detached
entity passed to persist: com.websopti.wotms.entity.Project; nested
exception is org.hibernate.PersistentObjectException: detached entity
passed to persist: com.websopti.wotms.entity.Project
i have Composite key join on entity basically i have two entity one is Project and one is User and i have created composite key join between them by making another Entity called ProjectUser following are classes
User
public class User extends BaseEntity<Long> implements UserDetails {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
...
#OneToMany(mappedBy="user",fetch=FetchType.LAZY)
private List<ProjectUser> userProjects;
...
getter and setters
}
Project
public class Project extends BaseEntity<Long> {
private static final long serialVersionUID = 7541005803335530236L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
...
#OneToMany(mappedBy="project",fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private List<ProjectUser> projectUsers;
...
}
ProjectUser
#IdClass(CompositeKey.class)
public class ProjectUser extends BaseEntity<Long> {
private static final long serialVersionUID = 476483195548055916L;
#ManyToOne
#Id
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
#ManyToOne
#Id
#JoinColumn(name="project_id", referencedColumnName="id")
private Project project;
private ProjectRole role;
...
getter setter
}
CompositeKey
public class CompositeKey implements Serializable {
private static final long serialVersionUID = 2186028617883601307L;
private long user;
private long project;
public CompositeKey() {}
public CompositeKey(long user, long project) {
this.user = user;
this.project = project;
}
public boolean equals(Object object) {
if (object instanceof CompositeKey) {
CompositeKey pk = (CompositeKey)object;
return user == pk.user && project == pk.project;
} else {
return false;
}
}
...
getter setter
}
now when i create project at that time if i set List object and save then it works fine but when i wanted to update that project and set Modified List object that is manually created by code and set to project object. so now when i try to save this modified project object then i get error for "detached entity passed to persist".
i'm doing like following and saving this project object
my code for saving project object controller method like follow
#RequestMapping(value="/update", method=RequestMethod.POST)
public String updateProject(#ModelAttribute("project") Project project, HttpServletRequest request, RedirectAttributes redirectAttributes){
try {
project.setIpAddress(CommonUtils.getClientIpAddr(request));
Project oldProject = projectService.findById(project.getId());
List<ProjectUser> newProjectUsers = new ArrayList<ProjectUser>();
List<Integer> selectedIndexes = project.getSelectedRoleIndexes();
List<User> users = project.getTeam();
if(users != null && users.size() > 0){
for (User u : users) {
newProjectUsers.add(new ProjectUser(u, project, ProjectRole.getRole(selectedIndexes.get(users.indexOf(u)))));
}
}
List<ProjectUser> oldProjectUsers = oldProject.getProjectUsers();
for (ProjectUser projectUser : new ArrayList<>(oldProjectUsers)) {
if(!users.contains(projectUser.getUser())){
/*disable all task for user*/
//taskService.disableUserTaskForProject(projectUser.getUser(), oldProject);
/*send remove member form project mail*/
//projectEmailService.sendProjectTeamMemberRemoveEmail(projectUser.getUser(), oldProject);
oldProjectUsers.remove(projectUser);
}else{
ProjectUser pu = newProjectUsers.get(users.indexOf(projectUser.getUser()));
oldProjectUsers.remove(projectUser);
projectUser.setRole(pu.getRole());
oldProjectUsers.add(projectUser);
}
}
List<User> oldTeam = oldProjectUsers.stream().map(pu -> {return pu.getUser();}).collect(Collectors.toList());
for (ProjectUser projectUser : newProjectUsers) {
if(!oldTeam.contains(projectUser.getUser())){
/*send user add in project mail*/
//projectEmailService.sendProjectTeamMemberAddEmail(projectUser.getUser(), oldProject);
oldProjectUsers.add(projectUser);
}
}
//oldProjectUsers = entityManager.merge(oldProjectUsers);
//projectUserService.updateAllProjectUsers(oldProjectUsers);
/*for (ProjectUser projectUser : oldProjectUsers) {
entityManager.merge(projectUser);
}*/
project.setProjectUsers(oldProjectUsers);
//project = entityManager.merge(project);
project = projectService.update(project);
/*old team except admin*/
/*List<User> oldTeam = oldProject.getProjectUsers()
.stream()
.map(pu -> {return pu.getUser();})
.collect(Collectors.toList());
List<User> newTeam = project.getTeam()
.stream()
.filter(u -> u.getRole() != SystemRole.ADMIN)
.collect(Collectors.toList());
project = projectService.update(project);
for (User user : oldTeam) {
if(!newTeam.contains(user)){
disable all task for user
taskService.disableUserTaskForProject(user, project);
send remove member form project mail
projectEmailService.sendProjectTeamMemberRemoveEmail(user, project);
}
}
for (User user : newTeam) {
if(!oldTeam.contains(user)){
send user add in project mail
projectEmailService.sendProjectTeamMemberAddEmail(user, project);
}
}*/
} catch(Exception e) {
e.printStackTrace();
return "redirect:/user/UserDashboard";
}
redirectAttributes.addFlashAttribute("projectId",project.getId());
redirectAttributes.addFlashAttribute("fromUpdate", true);
return "redirect:/user/"+PageTemplate.userDashboard;
}
please help me i'm stuck here

first of all thanks hansnae for giving me confidence to work on that question because i have visited that question 3 times and go through that question also have thinked to apply that solution but not applied because of i'm not sure about that.
now that bidirectional relation ship issue is there in my case so i have applied that logic for modification in List object and it worked for me
JPA/Hibernate: detached entity passed to persist
but in that question remove object facility not worked for me because i have used composite key join in my case i haev to work extra to remove that object
when i try to save removed object and set null in related joined entity then my case it throws Exception
No part of a composite identifier may be null
so to remove that object from list i have to manually remove that object by repository delete query.
Thanks for help and hands up too this great community

Related

Create mapped entity when you only have the id

I'm not sure how to phrase the question title to be honest, if someone has a suggestion, please let me know.
My use case is this, I have an entity with an account property like so (this is cleaned up to avoid clutter):
#Entity
#Table(name = "report_line", schema = "public")
public class ReportLine extends BaseReportLine {
#ManyToOne
#JoinColumn(name = "report_id")
private Report report;
#ManyToOne
#JoinColumn(name = "account_id")
private Account account;
}
But a DTO that only has an account id / different properties:
public class ImportLineDto {
public String groupName;
public Integer position;
public Integer parentPosition;
public String accountId;
public String name;
public BigDecimal amount;
public List<ImportLineDto> lines = new ArrayList<>();
}
I need to go through / flatten all lines so I can save it to a JPA repository, but there are 2 issues:
Is there a way to create the table line object using the accountId only, without having to look up the account for each line, as that will add a massive amount of unnecessary db calls.
What should I do with the 'lines' on each table object after flattening? Should I set them to null / empty list?
Is there a better way to do this? For once I can actually make changes to the code
Here is what I have so far:
private void saveReport(ImportedResult result) {
Report report = new Report();
...
report.setLines(getReportLinesFromDtoLines(result.lineItems.lines));
ReportRepository.saveAndFlush(report);
}
private List<ReportLine> getReportLinesFromDtoLines(ImportLineDto lines) {
List<ImportLineDto> flatLines = flatMapRecursive(lines).collect(Collectors.toList());
List<ReportLine> reportLines = new ArrayList<>();
for(ImportLineDto line: flatLines) {
ReportLine reportLine = new ReportLine();
reportLine.setItemText(line.name);
reportLine.setAmount(line.amount);
reportLine.setAccount(???);
// how do I set the 'Account' property using the id only, without looking up each account?
reportLines.add(reportLine);
}
return ReportLines;
}
public Stream<ImportLineDto> flatMapRecursive(ImportLineDto item) {
if (item.lines == null) {
return Stream.empty();
}
return Stream.concat(Stream.of(item), item.lines.stream()
.flatMap(this::flatMapRecursive));
}
Follow up:
Just to throw a wrench in there, what if the DTO accountId was not the actual "id" field in the table, but another custom field, I have another situation like that, would it even be possible? I still need the answer the the 1st question however with a standard id.
you may use entityManager.getReference as explained here
reportLine.setAccount(entityManager.getReference(Account.class, line.accountId));

ObjectBox: Get objects with specific relations

Expect the two entities Movie and Genre:
#Entity
public class Movie {
#Id
private long id;
private String name;
private ToMany<Genre> genres;
[...]
}
#Entity
public class Genre {
#Id
private long id;
private String name;
[...]
}
We all know how to create a relation and save it:
Movie movie = new Movie();
movie.setTitle("Star Wars");
movie.getGenres().add(new Genre("Sci-Fi");
box.put(movie);
but is there a possibility to query all Movie-objects with a specific Genre? Like
Box<Movie> box = boxStore.boxFor(Movie.class);
Query query = box.query()
.equal(Genre_.name, "Sci-Fi") // note that I want to query the Movie-Box with Genre-properties
.build();
List movies = query.find();
My goal is to find all movies with a specific genre in a simple way. Does anyone know how to do it or do I have to query all movies and filter the result on my own? Or do I have to adapt my entities in another way?
Update:
I prepared the correct marked answer below to a working example:
final Genre genreSciFi = genreBox.query().equal(Genre_.name, "Sci-Fi").build().findFirst();
List<Movie> filteredMovies = movieBox.query().filter(new QueryFilter<Movie>() {
#Override
public boolean keep(#NonNull Movie entity) {
return entity.getGenres().contains(genreSciFi);
}
}).build().find();
To make the contains-Method work correctly, override equals-Method in your Genre-Entity:
#Override
public boolean equals(Object obj) {
return obj instanceof Genre && ((Genre) obj).getId() == id && ((Genre) obj).getName().equals(name);
}
Unfortunately, this part of the API is not exposed in Java yet. We want to refactor the Query API very soon.
Until this is ready, you can workaround using query filtering. Example using Java/Kotlin-ish code for brevity:
Query query = movieBox.query().filter(movie -> {
return genres.contains(genre -> {
return "Sci-Fi".equals(genre.getName())
}
}).build()
(Will make it similar in Java with the next update.)

findRecord in Google CloudDatastore with Objectify

I want to use Objectify to query Google Cloud Datastore. What is an appropriate way to find a record based on a known key-value pair? The record is in the database, I verified this by Google's Datastore viewer.
Here is my method stub, which triggers the NotFoundException:
#ApiMethod(name="getUser")
public User getUser() throws NotFoundException {
String filterKey = "googleId";
String filterVal = "jochen.bauer#gmail.com";
User user = OfyService.ofy().load().type(User.class).filter(filterKey, filterVal).first().now();
if (user == null) {
throw new NotFoundException("User Record does not exist");
}
return user;
}
Here is the User class:
#Entity
public class User {
#Id
Long id;
private HealthVault healthVault;
private String googleId;
public User(String googleId){
this.googleId = googleId;
this.healthVault = new HealthVault();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public HealthVault getHealthVault() {
return healthVault;
}
public void setHealthVault(HealthVault healthVault) {
this.healthVault = healthVault;
}
public String getGoogleId() {
return googleId;
}
public void setGoogleId(String googleId) {
this.googleId = googleId;
}
}
I think it fails because of transaction. You need to make a transctionless call like:
User user = OfyService.ofy().transactionless().load().type(User.class).filter(filterKey, filterVal).first().now();
More info about transactions on App Engine:
https://cloud.google.com/appengine/docs/java/datastore/transactions
https://github.com/objectify/objectify/wiki/Transactions
EDIT
Your object needs #Index annotation. It will add field to datastore index. Only properties that are in the index can be searchable. Filter method is one of them.
#Id
Long id;
#Index
private HealthVault healthVault;
#Index
private String googleId;
P.S. delete your object with googleId jochen.bauer#gmail.com and write it again to database after you updated your entity. And objectify will find it.
First add #Index in your fields model. I didn't see filterVal as an email in your model. Even so, to get the entity based in your filterVal assuming that is googleId is the field of your entity.
User user = OfyService.ofy().load().type(User.class).filter("googleId", filterVal).now();
And so if your filterKey is the id of your entity.
User user = OfyService.ofy().load().key(Key.create(User.class, filterKey)).now();

Adding JPA Non primary key referenced ManyToOne entity

public class DemandItem extends BaseEntity{
#ManyToOne(cascade = {})
#JoinColumn(name = "sku_code",referencedColumnName = "sku_code", nullable = false)
private Inventory inventory;
public Inventory getInventory() {
return inventory;
}
public void setInventory(Inventory inventory) {
this.inventory = inventory;
}
public void setSkuCode(String skuCode){
if(this.inventory == null){
this.setInventory(new Inventory(skuCode));
}
}
public String getSkuCode(){
if(this.inventory != null){
return this.inventory.getSkuCode();
}
return null;
}
}
Now for creating a DemandItem I should first Load the inventory using skuCode and then set the inventory using setInventory. Just calling setSkuCode function causing transient entity exception. How to solve it,
I need to just get the skuCode filled while creating the data, but i dont need to load it while creating this entity.
While fetching I need the Inventory entity to be loaded.

Objectify not returns new data

I'm using Ojectify to store datastore, I have an entity that when I keep it if the changes are saved, but in the browser to display it sometimes shows me the previous data and sometimes the new data.
#Entity
public class BrandDto {
#Id
private Long id;
#Index
private String name;
#Index
private List<Ref<UserDto>> users;
#Index
private Integer numeroFollowers;
getters and setters .....
}
It happens in users and numeroFollowers fields.
I update this data as follows:
UserDto user = ofy().load().type(UserDto.class).id(p_userId).now();
if (user != null) {
BrandDto brand = ofy().load().type(BrandDto.class).id(p_brandId).now(); //get(p_brandId);
List<UserDto> users = brand.getUsers() != null ? brand.getUsers() : new ArrayList<UserDto>();
if (!users.contains(user)) {
users.add(user);
}
brand.setUsers(users);
brand.setNumeroFollowers(users.size());
ofy().save().entity(brand).now();
return true;
} else {
return false;
}
And I read as follows:
List<BrandDto> brands = ofy().load().type(BrandDto.class).list();
Other query that I use:
UserDto user = ofy().load().type(UserDto.class).id(p_userId).now();
Ref<UserDto> ref = Ref.create(user);
List<BrandDto> brands = ofy().load().type(BrandDto.class).filter("users", ref).list();
While get-by-key operations (like load().type(...).id(...)) are strongly consistent by default, queries are eventually consistent.
Here's more information: https://cloud.google.com/developers/articles/balancing-strong-and-eventual-consistency-with-google-cloud-datastore/

Categories