I am trying to find a way to retrieve the auto generated Id of an entity that is persisted in the database via cascade. I am using Hibernate 4.1.9, Spring data 1.2 and Spring framework 3.2.1. Here are the entities in question : Location, Home, Room.
Location parent class
#Entity
#Table(name = "location")
#Inheritance(strategy = InheritanceType.JOINED)
public class Location implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "location_id", unique = true)
private long uuid;
// other attributes and methods not relevant
}
Home class extending a Location, referencing a set of Rooms
#Entity
#Table(name = "home")
#Inheritance(strategy = InheritanceType.JOINED)
#PrimaryKeyJoinColumn(name = "home_id")
public class Home extends Location implements Serializable
{
#OneToMany(mappedBy = "containingHome", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Room> rooms;
// other attributes and methods not relevant
}
and finally the Room class referencing a Home object
#Entity
#Table(name = "room")
#PrimaryKeyJoinColumn(name = "room_id")
public class Room extends Location implements Serializable
{
#ManyToOne()
#JoinColumn(name = "home_id")
protected Home containingHome;
// other attributes and methods not relevant
}
I am using Spring data to create Repositories for the entities.
LocationRepository
public interface LocationRepository extends JpaRepository<Location, Long>
{ }
The problem I am having is that I need the id in order to be able to retrieve the different objects from the database and that is generated automatically. The only way I can access the id through the element is if I get the managed object when I save it to the database. But if I try to save each location in turn like so:
Home home = new Home();
home = locationService.save(home) // service that just calls locationRepository.save method
Room bedroom = new Room(home);
bedroom = locationService.save(bedroom);
I get a duplicate entry of room in the database which I think is related to a Hibernate issue https://hibernate.onjira.com/browse/HHH-7404. If I just call
Home home = new Home();
Room bedroom = new Room(home);
locationService.save(home)
there are no doubles but I have no way to retrieve the room object since it was persisted on cascade and its id is 0. Is there a way to solve this without introducing other fields in the location like a unique name that I have to generate myself? Any help is much appreciated.
Edit
If in the last case I have home = locationService.save(home) and then call home.getUuid() I get the right value which is normal I think since I retrieve a managed object. But if I do bedroom.getUuid() I get 0 since bedroom is not managed and so it has not had its id field updated with the value from the database.
Have you tried calling home.getUuid(); (assuming you have a getter for that field) after the persist call?
You might be surprised, but Hibernate (and JPA) will update the in memory copy with the id.
Related
I have two tables joined with a OneToOne relationship, one side exists in the database. When I insert the other side I want the first side's foreign key column to update so that it knows about the relationship without having to do it manually. Is this possible.
Here's my simplified example, I am using #MappedSuperclass because I have some shared fields in most of my Entities I included it here just in case it's causing an issue.
The base entity
#MappedSuperclass
#Data
public abstract class BaseEntity {
//defines some common fields I have in all my entities such Id
}
Abstract Image class
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
#Data
public abstract class Image extends BaseEntity {
//defines some other fields
public abstract UUID getTypeId();
}
UserProfilePhoto
#Entity
#Data
public class UserProfilePhoto extends Image {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userProfilePhoto")
private Profile profile;
#Override
public UUID getTypeId() {
return user.getId();
}
}
Profile
#Entity
public class Profile extends Base {
//defines some other fields
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn()
private UserProfilePhoto profilePhoto;
}
I'm looking for the following behavior:
When UserProfilePhoto is saved to image table (with Profile ID) the corresponding ImageId is updated in Profile
When Profile is Deleted the corresponding UserProfilePhoto is deleted from image table.
When UserProfilePhoto is deleted Profile remains but the foreign key column is nulled.
From researching this I think it's possible but it's a matter of getting the annotations correct. I've tried different combinations with no luck. Is what I'm looking for even possible?
No, it is not possible the way you describe it.
Any bidirectional relationship in JPA is controlled exclusively by the side indicated by mappedBy, so you need to update that side in your code, in order to have it persisted.
You can do that by invoking the other side in the setter, or by editing the other side in the first place.
My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);
I have a simple Invoice System where I need to create and update Invoices. I'm trying to use Spring Data Rest for that, but from what I get from the docs I really would end up doing a lot of calls to implement an update.
#Entity
class Article {
#Id
#GeneratedValue
Long ID;
#Basic
String name;
}
#Entity
class Invoice {
#Id
#GeneratedValue
Long ID;
#Basic
String customer;
#OneToMany(cascade=CascadeType.ALL)
List<InvoiceItem> items;
}
#Entity
class InvoiceItem {
#Id
#GeneratedValue
Long ID;
#Basic
double amount;
#ManyToOne
private Article article;
}
I'm using Spring Data Rest to expose this model to my web fronend via these Spring Data Rest/JPA Repositories
#RepositoryRestResource(collectionResourceRel = "articles", path = "articles")
interface ArticleJPARepository extends JpaRepository<Article, Long> {}
#RepositoryRestResource(collectionResourceRel = "invoices", path = "invoices")
interface InvoiceJPARepository extends JpaRepository<Invoice, Long> {}
#RepositoryRestResource(collectionResourceRel = "invoiceitems", path = "invoiceitems")
interface InvoiceItemJPARepository extends JpaRepository<InvoiceItem, Long> {}
So let's say I have an update form that looks like this:
where I can do several things:
Update Invoice customer
Change an Article of an existing InvoiceItem
Remove some of the InvoiceItems
Append a new InvoiceItem
From what I get from the Spring Rest Docs is that I need to make several calls now.
Update of the invoice customer I need to PUT /invoices/1
then update n the Article and amount in an InvoiceItem PUT n times PUT /invoiceitems/<x>/
Append m new InvoiceItems m times POST /invoiceitems then POST /invoices/1/items
Delete InvoiceItem 2 DELETE /invoiceitems/2 and then DELETE /invoices/1/items/<2>
Is there a simpler way to realise such an update with Spring Data Rest?
I am trying to utilize a NamedEntityGraph to limit the return data for specific queries. Mainly I do not want to return full object details when listing the object. A very simple class example is below.
#Entity
#Table(name="playerreport",schema="dbo")
#NamedEntityGraphs({
#NamedEntityGraph(name = "report.simple",
attributeNodes =
{#NamedAttributeNode(value="intId")
}
)
})
public class PlayerReportEntity {
#Id
#Column(name="intid",columnDefinition="uniqueidentifier")
private String intId;
#Column(name="plyid",columnDefinition="uniqueidentifier")
#Basic(fetch=FetchType.LAZY)
private String plyId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "plyid", insertable=false,updatable=false)
private PlayerEntity player;
No matter what I do to plyId and player are always returned. Is there any way to only return the requested columns (intId) ?
As for the collection Hibernate does not do the join for the player object but it still returns player as null. So that part is working to an extent.
I am using a JPARepository below to generate Crud Statements for me
public interface PlayerReportRepository extends JpaRepository<PlayerReportEntity, String> {
#EntityGraph(value="report.simple")
List<PlayerIntelEntity> findByPlyId(#Param(value = "playerId") String playerId);
#Override
#EntityGraph(value="report.simple")
public PlayerIntelEntity findOne(String id);
}
A chunk of text from here - "Hence it seems that the #NamedEntityGraph only affects fields that are Collections, but fields that are not a Collection are always loaded." from JIRA
Please use the Example 47 on this page and use repositories accordingly.
In essence, hibernate is right now loading all the feilds in the class and for collections it will work if you follow the example stated above.
Thanks.
I am using spring mvc with hibernate and JPA. I have a Person class which is inherited by another class called Agent. The mapping is implemented as follows:
#Entity
#Table(name = "Person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person extends Auditable implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PersonId")
protected Long id;
//other variables
...
}
#Entity
#PrimaryKeyJoinColumn(name = "PersonId")
public class Agent extends Person implements Serializable {
//additional agent specific variables go here
...
}
Saving new data is smooth and I have no problem there. however, when I edit data, everything except the id value is bound to the controller method's model attribute. I have verified that the id has been sent along with other items from the browser using chrome's developer tools. but the id field at the controller is always null and as a result the data is not updated. This is what my controller method looks like:
#RequestMapping(value = "register", method = RequestMethod.POST)
public #ResponseBody CustomAjaxResponse saveAgent(ModelMap model, #ModelAttribute("agent") #Valid Agent agent, BindingResult result) {
...
}
I suspect the problem is probably with my inheritance mapping because I have other classes inheriting from the Person class and I face a similar problem there as well.
Please help!
you need a public setter for id.
In cases like this I commonly use a specific dto for the form, and/or implement a conversion service that retrieves the entity via hibernate based on id and then performs a merge.