Best practice to prepare entity for view in Spring mvc - java

I have a JPA entity that contains collections of another entities instances.
I need to remove some of the instances from the collection and change other stuff, just for View and I don't want to change my database content.
What is the best way to do it?
Make a clone of my object and work with it.
Remove lazy load (or get all that I need from this main bean). Then close hibernate session, and work with the detached object.
Anything else?
UPDATE
My bean
#Entity
#Table(name = "client")
public class Client extends AbstractPersistentEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CLIENTS_SEQ")
#SequenceGenerator(name = "CLIENTS_SEQ", sequenceName = "clients_seq")
private Integer id;
#NotEmpty
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "clientId")
private Collection<ContactPhones> contactPhonesCollection;
}
And I want to remove some of ContactPhones for view. But it can be much complicated, may be in ContactPhones will be another collection and I want to remove it. Something like that.

If you don't want to actually remove any row from the database, in my opinion the best choice is to detach the entity from the session and work with it as with any other Java object.

Related

Map an entity based on one of two different attributes (either one or the other)

I'm developing a web app using Java, Spring and JPA. The use case I would like to discuss with you is quite simple, it just consists of two entities, Wallet and Transaction. In particular a Wallet can have two kind of transactions, incoming and outgoing. Let's see the structure of these two classes.
#Entity
public class Wallet{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long walletId;
#OneToMany(mappedBy="fromWallet", orphanRemoval = true)
private List<Transaction> outgoingTransactions;
#OneToMany(mappedBy="toWallet", orphanRemoval = true)
private List<Transaction> incomingTransactions;
....
}
public class Transaction{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long transactionId;
#ManyToOne
#JoinColumn(name = "wallet_id_from")
private Wallet fromWallet;
#ManyToOne
#JoinColumn(name = "to_wallet_id")
private Wallet toWallet;
....
}
This is what my code looks like right now, and it works perfectly. My question is, how can I change the code so that instead of having two different lists I would have just one with both incoming and outgoing transactions? So what I would like is something like this:
#Entity
public class Wallet{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long walletId;
#OneToMany(mappedBy="{'fromWallet' or 'toWallet'}", orphanRemoval = true)
private List<Transaction> transactions;
....
}
Is it possible to do something like this? Or I must stick to the current solution (two different list)?
I don't think the situation you are describing can be achieved with JPA tools, but you can of course do something like
Remodel your database and merge both join columns so that you end up with a "classical" OneToMany relationship with the downside that you lost the information if the wallet was from or to.
Create a getTransactions method in Wallet that simply returns a merged immutable list of outgoingTransactions and incomingTransactions without having the downside of 1.

Dynamic fetching for relations in Spring JPA

I want to be able to dynamically load the relations of my entity, depending on which RestService got called.
Entity classes:
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private Buyer buyer;
// some more attributes
}
#Entity
public class Buyer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// some more attributes
}
RestController class:
#GetMapping
public Iterable<Order> getAll() {
// here I want JPA to NOT load the buyers for the order
return orderRepository.findAll();
}
#GetMapping("/{id}")
public Order get(#PathVariable("id") String id) {
// here I want JPA to load the buyers for the order
return orderRepository.findById(Long.parseLong(id)).orElseThrow();
}
None of the two fetchtypes LAZY and EAGER or json annotations (like #JsonIgnore, #JsonIdentityInfo, #JsonManagedReference and #JsonBackReference) seem to make this possible as far as I understood and tried.
If this is not possible, maybe someone can explain how to solve this problem then. On the one hand I sometimes need those relations in my frontend to display some values and on the other hand when I always load them I get huge performance problems or infinity recursions.
I don't think JPA supports your use case directly.
One option is to create the same entity twice - one with eager and the other with lazy. Switch them in the methods.
Another option is to use a DTO (Data Transfer Object) as the response, instead of the entity class itself. You will have to write a mapper logic to convert an entity to DTO though.

JPA/validation #ManyToOne relations should not create new rows

I have an JPA entity with contains a ManyToOne reference to another table, a simplified version of that entity is shown below:
#Entity
#Table(name = "ENTITIES")
public class Entity implements Serializable {
#Id #NotNull
private String id;
#JoinColumn(name = "REFERENCE", referencedColumnName = "ID")
#ManyToOne(optional = false)
private ReferencedEntity referencedEntity;
}
#Entity
#Table(name = "REFERENCES")
public class ReferencedEntity implements Serializable {
#Id #NotNull #Column(name = "ID")
private String id;
#Size(max = 50) #Column(name = "DSC")
private String description;
}
Finding entities works fine. Peristing entities also works fine, a bit too good in my particular setup, I need some extra validation.
Problem
My requirement is that the rows in table REFERENCES are static and should not be modified or new rows added.
Currently when I create a new Entity instance with a non-existing (yet) ReferencedEntity and persist that instance, a new row is added to REFERENCES.
Right now I've implemented this check in my own validate() method before calling the persist(), but I'd rather do it more elegantly.
Using an enum instead of a real entity is not an option, I want to add rows myself without a rebuild/redeployment several times in the future.
My question
What is the best way to implement a check like this?
Is there some BV annotation/constraint that helps me restrict this? Maybe a third party library?
It sounds like you need to first do a DB query to check if the value exists and then insert the record. This must be done in a transaction in order to ensure that the result of the query is still true at the time of insertion. I had a similar problem half a year back which might provide you with some leads on how to set up locking. Please see this SO question.
You should add this => insertable=false, updatable=false
And remove => optional=false , and maybe try nullable=true

How to use hibernate to query for an object with a nested object that has a nested collection of objects

Using Hibernate, I need to query a MySQL database for a Post entity that has a one-to-one relationship with a Poll entity that has a one-to-many relationship with an Answer entity. I need the Post object to contain the Poll object and its Poll object to contain its Answer objects. Here's the basic class setup:
Update:
The Post table must not have a primary key column. It is a waste of data. I need to be able to get Post objects from the database using the user_id column. Getting Post objects using the user_id column is the only way it will ever be done, so it makes no sense for me to have a primary key column. So if you're going to provide an answer that provides insight into a solution that solves my problem, please keep those specifications in mind.
Post Class:
#Entity
#Table(name="user_feed")
public class Post implements Serializable {
//id for the user that is meant to receive the post
//*post object is taken from a table that will contain
//*posts for many different users
#Id
#Column(name="user_id")
private long mUserId;
//poll id
#Id
#Column(name="poll_id")
private long mPollId;
//boolean that indicates whether this post is a repost
#Column(name="is_repost")
private boolean mIsRepost;
//date the post was created
#Column(name="date_created")
private Date mDateCreated;
//the poll this post contains
#OneToOne
#JoinColumn(name="poll_id")
private Poll mPoll;
Poll Class:
#Entity
#Table(name="poll")
public class Poll implements Serializable{
//the poll's id
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
//id of the user who created the poll
#Column(name="user_id")
private long mUserId;
//the text of the poll's question
#Column(name="question")
private String mQuestion;
//the date the poll was created
#Column(name="date_created")
private Date mDateCreated;
//the answer objects for this poll
#OneToMany
#JoinColumn(name="id")
private List<Answer> mAnswers;
Answer Class:
#Entity
#Table(name="answer")
public class Answer implements Serializable {
//id for a particular answer
//*this is not a necessary value for the application logic, but
//*Hibernate forces me to designate an #Id annotation for every
//*entity, so I created this field and the associated column in
//*the database
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
//the answer's text
#Column(name="answer_text")
private String mAnswer;
//the id of the poll to which this answer pertains to
#Column(name="poll_id")
private long mPollId;
***I'm confused about the id for this table. It doesn't make sense for each answer to have a primary key, but Hibernate requires some sort of #Id annotation in the class, so I decided to just create a primary key column in the table for the sake of Hibernate. It's never used. I would like to get rid of it, but there really isn't anything that makes one Answer unique from another for the same poll except for their text at the moment -- it's not necessary for the application logic.
Query I came up with: doesn't work
.
This query was really just me testing to see if I could get a single Post object with all of its nested objects. I knew if I could get one, getting a collection wouldn't be much more of a stretch -- but I can't even get one.
Session session = HibernateUtilities.openSession();
session.beginTransaction();
//29 is a post meant for a particular user and 47 is the id of the
//poll that should be contained in the post
Post post = (Post)session.get(Post.class, new Post(29, 47));
session.getTransaction().commit();
session.close();
//suppose to return the post in JSON format to a client, but it
//doesn't work when I create the one-to-many relationship between
//the poll and it's answers. It only works without the relationship;
//which I've defined in the Poll class
return mGson.toJson(post);
You shouldn't put the primary keys of the relationships as fields of their own (e.g. you don't need both Post.mPoll and Post.mPollId, just use Post.mPoll.getId() if you need it). If I were to address your problem I would by default (we can discuss Post not having an id later) use the following object model (getters omitted for brevity but I would have them on all fields).
#Entity
public class Post {
#Id
#GeneratedValue
private long id;
#OneToOne
private Poll poll;
}
#Entity
public class Poll {
#Id
#GeneratedValue
private long id;
#OneToMany
private List<Answer> answers;
}
#Entity
public class Answer {
#Id
#GeneratedValue
private long id;
}
Start from there and see where it falls apart. If you want an entity to not have any ID then you can use the #Embedded, #Embeddable, and #ElementCollection annotations.
#Embeddable was originally meant for embedding "value" objects (e.g. things like currency, dates, postal addresses, etc.) and as such these objects do not need a primary key and are completely owned by their owning entity.
You reference the embeddable object with the #Embedded annotation (e.g. your User would have an #Embedded reference to the #Embeddable post if it were a one-to-one).
To reference a collection of embeddable objects you use the #ElementCollection annotation. However, members of an #ElementCollection are immutable (can't modify them in the database, have to remove it from the collection and add a new instance) and cannot be lazily loaded. Given the complexity of your Post object I would not personally make it an embedded class (you may want the ability to edit a post someday?) but if you want to it should work.
I say should because I have never had an embeddedable class that references other non-embeddable entities (e.g. your reference to the Poll). Give those things a try and if they don't work then please post exactly what is going wrong.
Solved it myself. All the comments in the below code designate the changes I made to the code I presented in the question and explain why I made them.
Post Class:
#Entity
#Table(name="user_feed")
public class Post implements Serializable {
#Id
#Column(name="user_id")
private long mUserId;
//removed long mPollId
//hibernate is capable of getting the foreign key for a post's
//poll_id column from its poll object -- mPoll
//so i don't have to have a separate field for the id of this post's
//poll
#Column(name="is_repost")
private boolean mIsRepost;
#Column(name="date_created")
private Date mDateCreated;
//made this field part of the composite id instead of long mPollId
//pretty much the same composite key as before just had to alter
//my implementation of Post.equals(Object) to use this poll's id
//instead of this class's mPollId field
//implementing your own .equals(Object) method is necessary when
//creating composite keys as i do with multiple #Id annotations
//i think you also have to implement your own .hashCode() method too
//but the word hash scares me, so I didn't do it
//the code works, so i'm just gonna let it rock
#OneToOne
#JoinColumn(name="poll_id")
private Poll mPoll;
Poll Class:
#Entity
#Table(name="poll")
public class Poll implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
#Column(name="user_id")
private long mUserId;
#Column(name="question")
private String mQuestion;
#Column(name="date_created")
private Date mDateCreated;
//removed #JoinColumn -- not completely sure about why it wasn't
//helping, but many of the examples similar to my use case didn't
//use it so I got rid of it
//added mappedBy variable -- still not really sure what it does
//but it works
//and added FetchType.EAGER so everytime a Poll object is loaded
//the answers it's associated with are loaded too
#OneToMany(mappedBy="mPoll", fetch=FetchType.EAGER)
#Cascade({CascadeType.SAVE_UPDATE, CascadeType.REMOVE})
private List<Answer> mAnswers;
Answer Class:
#Entity
#Table(name="answer")
public class Answer implements Serializable {
//turns out having a primary key on the answer table is actually useful
//for the application logic. would you look at that
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long mId;
#Column(name="answer_text")
private String mAnswer;
//got rid of long mPollId
//it was for the same reason i did in the Post class
//hibernate does the work for me with the mPoll object and the
//annotations i've provided on it
//made the relationship between a poll and its answers bidirectional
//not entirely sure how adding the below annotations to the new
//Poll field fixed my problems, but it did
//i imagine it somehow tells hibernate that the primary key
//for the below object is the foreign key represented by poll_id in the
//database table for this entity
//and making insertable=true enables hibernate to insert that foreign
//key into the appropriate column in the database when this entity
//is saved
//updatable seemed to be necessary
//hibernate complained when it wasn't there
//and nullable was in the helpful examples i found so it was copy and
//pasted along with the rest of the helpful stuff here
//this field can't be nullable anyways so semantically, it makes sense
//for it to be there
#ManyToOne
#JoinColumn(name="poll_id", nullable = false, insertable=true, updatable=false)
private Poll mPoll;
Final functioning query: does work
Session session = HibernateUtilities.openSession();
session.beginTransaction();
List<Post> usersFeed = session.createQuery("select p from Post p where p.mUserId = :userId")
.setString("userId", userId)
.list();
session.getTransaction().commit();
session.close();

Seam/Hibernate/JPA -- Duplicate primary key exception?

I've got an object model that is persisted using Seam and JPA (Hibernate). It looks something like this:
#Entity(name = "MyObject")
public class MyObject {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_myobj")
#SequenceGenerator(name = "seq_myobj", sequenceName = "seq_myobj")
private Long id = null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#NotNull
private MySubObject subObjA=null;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#NotNull
private MySubObject subObjB=null;
...
}
#Entity(name = "MySubObject")
public class MySubObject {
...
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_mysubobj")
#SequenceGenerator(name = "seq_mysubobj", sequenceName = "seq_mysubobj")
private Long id = null;
}
I've defined my #ManyToOne annotations correctly and everything. However, if I try and persist an instance of MyObject where both subObjA and subObjB are set, I get an exception saying I've got a duplicate primary key one of the sub obj's. What would cause this behavior? Both objects have their identifier types set to SEQUENCE, and I have no problem if I set one or the other. It's only when I set both that I get the exception.
I'm running Seam 2.2 and my backend database is PostgreSQL. Any thoughts on what could be causing this strange behavior? I thought both objects would be persisted as part of the same transaction and that the correct primary keys would be assigned automatically. Like I said, if I only set one of the objects there is no issue. It only happens when I set them both. Any help you can give would be GREATLY appreciated.
EDIT I've noticed some strange behavior in testing out various things, however. If I create MyObject programmatically and set all of its properties (including subObj) it persists with no problem. However, if I enter the properties using a form, I get the error. Could it have something to do with transactions?
If you override equals/hashCode in MySubObject class be sure that these methods only check the surrogate key id (in such a case you should avoid them completely).
If equals/hashCode methods work with some business key properties, make sure that these keys are unique before persisting.
Ran through a battery of tests and different scenarios, and have found occasions when it works. So, it looks like there is a bug in my action class when I got to submit and persist to the database.

Categories

Resources