how to develop JPA bi-directional entities in spring toot - jpa

I am new to spring boot and jpa/hibernate, please bear my inaccurate usage of the terminologies.
I have two entities: book and address. A book is published in a certain city which is stored in "address", a "address" can publish multiple books.
The DB schema for book is: id, name, author, price, addressid
schema for address: addressid, addressCountry, addressCity
The entity for book:
#Entity
#Table(name = "test_book")
public class Book implements Serializable {
private static final long serialVersionUID = 8025948150436422040L;
#Id
long id;
#Column(name = "name")
String name;
#Column(name = "author")
String author;
#Column(name = "price")
long price;
#ManyToOne
#JoinColumn(name = "addressid")
private Address address;
...//getter and setter
The entity for address
#Entity
#Table(name = "test_address")
public class Address implements Serializable{
private static final long serialVersionUID = -3541059157210384355L;
#Id
#Column(name= "addressid")
private long addressId;
#Column(name="addresscountry")
private String addressCountry;
#Column(name="addresscity")
private String addressCity;
#OneToMany(mappedBy = "address")
private Collection<Book> books;
...//getter setter
But when I call the Restful service, I get infinite loop...
[{"id":11,"name":"Java Book","author":"Jame Gosling","price":100,"address":{"addressId":1,"addressCountry":"China","addressCity":"Shanghai","books":[{"id":11,"name":"Java Book","author":"Jame Gosling","price":100,"address":...
I did some search. And my request is:
when I search a book, I can get the information: id, name, author, price, address..
And also I can query a address to get all the books the city published.
When I add Json Annotation #JsonManagedReference in address and #JsonBackReference in book entity, I can query book but cannot get address information.
Could you please help how to solve the problems? Thank you very much.

You can ignore the #JsonIgnore on getter for books. This will exclude the Collection<Book> books property from serialization the Address.
Link : JacksonAnnotations - Faster XML Wiki
Example:
#JsonIgnore
public Collection<Book> getBooks() {
...
}

#JsonIgnore
The Jackson annotation #JsonIgnore is used to tell Jackson to ignore a
certain property (field) of a Java object. The property is ignored
both when reading JSON into Java objects, and when writing Java
objects into JSON.
In your case this is happening as there is bidirectional relationship and so it will go into loop. To stop this you need to provide JsonIgnore
And so your code will be like :
-> The entity for address
#Entity
#Table(name = "test_address")
public class Address implements Serializable{
private static final long serialVersionUID = -3541059157210384355L;
#Id
#Column(name= "addressid")
private long addressId;
#Column(name="addresscountry")
private String addressCountry;
#Column(name="addresscity")
private String addressCity;
#OneToMany(mappedBy = "address")
#JsonIgnore
private Collection<Book> books;

Related

JPA query method to find by elements in a List?

I have the following two entities (Contact and Participation, linked by a ManyToMany relation) :
#Entity
public class Contact {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
#ManyToOne
private Company company;
#ManyToMany(fetch=FetchType.EAGER)
private List<Participation> participations;
}
#Entity
public class Participation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Company company;
private Status status;
}
I can't figure out how to get Contacts who have a specific Participation in their list. Should I look via Contacts with a specific JPA repository method (findBy...) ? Or would i have to look via the table which was created with both Contact and Participation IDs (ManyToMany) ?
Thanks!

The attribute [] is not present in the managed type

I'm doing an application that has this relation ship: A personal contact has an Email.
So i'm trying to find the Emails from the personal contact and I'm doing this query using Criteria but always return IllegalArgumentException:
#Override
public Email findByEmail(PersonalContact personalContact) {
CriteriaBuilder criteriaBuilder = entityManager().getCriteriaBuilder();
CriteriaQuery<Email> criteriaQuery = criteriaBuilder.createQuery(Email.class);
Root<Email> email = criteriaQuery.from(Email.class);
criteriaQuery.where(criteriaBuilder.equal(
email.get("personalContact"), criteriaBuilder.parameter(PersonalContact.class, "personalContact")));
TypedQuery<Email> typedQuery = entityManager().createQuery(criteriaQuery);
typedQuery.setParameter("personalContact", personalContact);
return typedQuery.getSingleResult();
}
Personal contact is like a foreign key.
And here is my Email class:
#Entity
#Table(name = "Email")
public class Email implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String mainEmail;
private List<String> secondaryMail;
#JoinColumn(name = "personal")
#OneToOne(fetch = FetchType.LAZY)
private PersonalContact pContact;
and here is my Personal Contact class:
#Entity
#Table(name = "PERSONALCONTACT")
public class PersonalContact extends Contact implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PERSONAL_ID")
private Long id;
//Other variables
#OneToOne(fetch=FetchType.LAZY, mappedBy="personal")
private Email email;
And every time I execute the query this is the return:
Exception in thread "AWT-EventQueue-0"
java.lang.IllegalArgumentException: The attribute [personalContact] is
not present in the managed type [EntityTypeImpl#1230307250:Email [
javaType: class csheets.ext.crm.contact.Email descriptor:
RelationalDescriptor(csheets.ext.crm.contact.Email -->
[DatabaseTable(Email)]), mappings: 5]].
I did some search and the others programmers said the problem was on the name of the variables... but i guess the names of the variables are correct.
So what I'm doing wrong? perhaps the relationship between that two classes?
Thank you!
If you read the exception message carefully, you'll find that it is complaining that class Email does not have a property (attribute) called personalContact, and indeed, there is no such property. Presumably you meant the pContact property?
(Mistakes such as this are why I recommend querying JPA via Querydsl: code completion would likely have prevented this mistake, and even if not, you would have gotten a clear compiler message when trying to use a non-existing property)

How to map existing JPA entities to PicketLink

I am trying to migrate a Seam 2 app to CDI and use PicketLink for security. After all the reading and researching, it seems like all the examples are having one to one mapping between PicketLink model and the backend entity. e.g. Account to AccountEntity, Partition to PartitionEntity. Since I already have entities in place representing identity model, I am stuck on trying to map them to PicketLink. Here is what I have:
#MappedSuperClass
public class ModelEntityBase implement Serializable {
#Id #Generated
Long id;
Date creationDate;
}
#Entity
public Account extends ModelEntityBase {
String username;
String passwordHash;
#OneToOne(mappedBy = "account")
Person person;
}
#Entity
public Person extends ModelEntityBase {
String name;
String email;
#OneToOne
#JoinColumn(name = "account_id")
Account account;
}
Two entities (plus a super class) representing a single identity model in PicketLink, e.g. stereo type User.
Based on this why IdentityType id is String not Long, I tried to add a new Entity in:
#Entity
#IdentityManaged(BaseIdentityType.class);
public class IdentityTypeEntity implement Serializble {
#Id #Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType")
#OwnerReference
private Account account;
#IdentityClass
private String typeName;
#ManyToOne #OwnerReference
private PartitionEntity partition;
}
I've tried a few different ways with the annotation and model classes. But when using IdentityManager.add(myUserModel), I just can't get it to populate all the entities. Is this even possible?
Got help from Pedro (PicketLink Dev). Post the answer here to help others.
This is the model class I ended up using.
#IdentityStereotype(USER)
public class User extends AbstractAttributedType implements Account {
#AttributeProperty
private Account accountEntity;
#AttributeProperty
#StereotypeProperty(IDENTITY_USER_NAME)
#Unique
private String username;
#AttributeProperty
private boolean enabled;
#AttributeProperty
private Date createdDate;
#AttributeProperty
private Date expiryDate;
#AttributeProperty
private Partition partition;
// getter and setter omitted
}
And created a new entity to map to this model:
public class IdentityTypeEntity implements Serializable {
#Id
#Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType",
cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#AttributeValue
// #NotNull
private HAccount accountEntity;
#IdentityClass
private String typeName;
#ManyToOne
#OwnerReference
private PartitionEntity partition;
#AttributeValue
private String username;
#AttributeValue
// #Transient
private boolean enabled;
#AttributeValue
private Date createdDate;
#AttributeValue
private Date expiryDate;
}
PL can map property with #AttributeProperty to entity property with #AttributeValue. But it can only map to one entity. Therefore there is no way to map, say User and its properties over to Account and Person. But you can have the entity (in my case accountEntity) in the model. I also have to duplicate a few fields in the new IdentityTypeEntity and my existing Account entity (username, eanbled, createdDate) because PL requires these. Use a #PrePersist and similar to sync them.

JPA JoinColumn does not work

I have started with an example from a book. It was really a dummy oriented one and I am having a problem with the code the book gave.
Here is the code;
#Entity
public class Customer {
#Id
#GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String email;
#OneToOne (fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name = "address_fk")
private Address address;
//getter, setter, Constructor
--//-------------------------------------
#Entity
public class Address {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private String id;
private String city;
private String street;
private String number;
In here it is complaining on the JoinColumn annotation. It says the column address_fk is not found.
Is this an IDE related issue? Am I missing something?
Edit : No table yet created in the DB. I am expecting them to be seen on the DB automatically by my persistence.xml
<property name="eclipselink.ddl-generation" value="create-tables"/>
Using #JoinColumn annotation to specify foreign column name to be address_fk instead of default address_id is fine.
What likely does not to work is #GeneratedValue with String. According specification only integral types as generated primary keys are portable.

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,