JPA Many to Many trouble - java

I have been reading and watching tons of tutorials and just can't seem to get the many to many relationship to work. I have built a new project to simplify the project down to one many to many relationship. Please take a look at the code below and lend some suggestions as to why this fails. Currently I get a null pointer at the *keywd.getMaterialRecordList().add(record); line. If I comment this out then I get the same null pointer on the next .get....add();. If it matters I have abandoned Derby and moved to an H2 database.
#Entity
#Table(name = "MATERIAL_RECORD")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "MaterialRecord.findAll", query = "SELECT m FROM MaterialRecord m")})
public class MaterialRecord implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "ID")
private Integer id;
#Column(name = "SDS_NUMBER")
private Integer sdsNumber;
#Column(name = "PRODUCT_NAME")
private String productName;
#ManyToMany(mappedBy = "materialRecordList")
private List<Keywords> keywordsList;
#Entity
#Table(name = "KEYWORDS")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Keywords.findAll", query = "SELECT k FROM Keywords k")})
public class Keywords implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "KEY_ID")
private Integer keyId;
#Column(name = "KEYWORD_NAME")
private String keywordName;
#JoinTable(name = "KEYWORD_LOOKUP", joinColumns = {
#JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID")}, inverseJoinColumns = {
#JoinColumn(name = "SDS_NUMBER", referencedColumnName = "SDS_NUMBER")})
#ManyToMany
private List<MaterialRecord> materialRecordList;
public class JpaTest {
public static void main (String [] args){
MaterialRecord record = new MaterialRecord();
record.setProductName("oofbar");
Keywords keywd = new Keywords();
keywd.setKeywordName("testing");
Keywords keywd2 = new Keywords();
keywd2.setKeywordName("testing2");
record.getKeywordsList().add(keywd);
record.getKeywordsList().add(keywd2);
keywd.getMaterialRecordList().add(record);
keywd2.getMaterialRecordList().add(record);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("JavaApplication26PU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(record);
em.persist(keywd);
em.persist(keywd2);
tx.commit();

Ok here are my thoughts.
To avoid the null pointer exception as the entity is not retrieved from the persistence context the Collection in this case the list has not been initialized, so use this.
#ManyToMany(mappedBy = "materialRecordList")
private List<Keywords> keywordsList = new ArrayList<Keywords>();
Or if you dont want to use that approach use setter method to create and set the list before try to fill it. That will avoid null pointer exceptions and your example should work. =)
This only is needed as you are creating your own entity from scratch, If you use find that list will be filled and created automatically.

I think the problem is with relation. ManyToMany relation need to be with primary keys of two table.
try this :
#JoinTable(name = "KEYWORD_LOOKUP", joinColumns = {
#JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID")}, inverseJoinColumns = {
#JoinColumn(name = "SDS_NUMBER", referencedColumnName = "ID")})
#ManyToMany
private List<MaterialRecord> materialRecordList;
update me if you still get some problem.

Related

How to convert collection of entities relation into collection of primitive ids in JPA/Hibernate?

I have two entities connected with many-to-many relationship. For example:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Game> games = new HashSet<>();
}
#Entity
public class Game {
#Id
private Long id;
#ManyToMany(mappedBy = "games", fetch = FetchType.LAZY)
List<Account> accounts = new ArrayList<>();
}
So, there is a table account_games(account_id, game_id) in mysql describing entities many-to-many relations.
I don't want to have Game entity anymore. Is there a way to get rid of Game and leave gameId relation only? So, I'd like to have code something like that:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Long> gameIds = new HashSet<>();
}
without making changes in database.
I've tried different configuration on javax.persistance annotations, but none worked
You can use #ElementCollection and #CollectionTable to achieve that.
#Entity
public class Account {
#Id
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "account_games", joinColumns = #JoinColumn(name = "account_id"))
#Column(name = "game_id", nullable = false)
private Set<Long> gameIds = new HashSet<>();
}
You may have to change the query on how to filter data using gameId. Element Collection Query

Inheritance with hibernate ManyToMany relation

I have a problem.
I am writing program which is connecting to database, where I cant change anything. I can only read data from it. So let's say I have three tables MOVIES, BOOKS, REVIEWDOCUMENT and two many to many tables MOVIES_REVIEWDOCUMENT plus BOOKS_REVIEWDOCUMENT.
Because I am using Spring Boot with Hibernate, I have written simple entities classes.
#Entity(name = "MOVIES")
#Table(name = "MOVIES")
public class Movies {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "MOVIE_KEY")
private String movieKey;
#Column(name = "TYPE_ANIMATED")
private String typeAnim;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "MOVIES_REVIEWDOCUMENTS",
joinColumns = #JoinColumn(name = "MOVIE_KEY"),
inverseJoinColumns = #JoinColumn(name = "DOCUMENT_KEY"))
private List<ReviewDocuments> reviewDocuments;
}
#Entity(name = "BOOKS")
#Table(name = "BOOKS")
public class Books {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "BOOK_KEY")
private String bookKey;
#Column(name = "TYPE_WRITTEN")
private String typeWritten;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "BOOKS_REVIEWDOCUMENTS",
joinColumns = #JoinColumn(name = "BOOK_KEY"),
inverseJoinColumns = #JoinColumn(name = "DOCUMENT_KEY"))
private List<ReviewDocuments> reviewDocuments;
}
#Entity(name = "REVIEWDOCUMENTS")
#Table(name = "REVIEWDOCUMENTS")
public class ReviewDocuments {
#Id
#Column(name = "OBJID")
private Long objId;
#ManyToMany(mappedBy = "reviewDocuments")
private Set<Movies> movies;
#ManyToMany(mappedBy = "reviewDocuments")
private Set<Books> books;
}
And it is working pretty ok. But because as you can see MOVIES and BOOKS are almost indentical, only diffrence are those relational tables. I was wondering if I can somehow extract it to abstrac class.
Because what I need to do is to create service class which will iterate after books/movies and it's documents and do some operation on reviewDocuments. So easies way would be creating generic service for each of entities.
Example:
public void extractData() throws IOException {
Path tempDirectory = Files.createTempDirectory("zip_");
tempDirectory.toFile().deleteOnExit();
Set<Book> books = movieRepository.findByRidKey(extractionParameters.getWriteNumber());
for (Book book :
books) {
for (ReviewDocuments documents :
book.getReviewDocuments()) {
exportDataToFile(data);
}
directoryToZip(tempDirectory.toFile(), book.getId());
FileUtils.cleanDirectory(tempDirectory.toFile());
}
FileUtils.deleteDirectory(tempDirectory.toFile());
}
I don‘t think you can use inheritance with multiple many-to-many tables.
You could however define a common interface and implement your service based on that interface.

Bidirectional #ManyToMany doesn't remove records from the join table xx_yy with CascadeType.ALL

I'm implementing categorisation system where a category will usually have several subcategories, and a subcategory will have at least one parent, but there will certainly be cases when a subcategory will have more than one parent.
That's why I chose ManyToMany approach.
So, the Category:
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "cat_id", nullable = false)
private Integer catId;
....
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "cats_subcats",
joinColumns = #JoinColumn(name = "cat_id"),
inverseJoinColumns = #JoinColumn(name = "subcat_id")
)
private Set<Subcategory> subcats;
....
The Subcategory:
public class SubCategory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "subcat_id", nullable = false)
private Integer subcatId;
....
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "subcats")
private Set<Category> cats;
....
This setup works, it creates the join table, inserts my two dummy subcats, and also creates the two joining records in the join table.
I then proceeded with testing how it would behave in different scenarios.
First, I wanted to remove one subcategory from an existing category with three subcategories.
My managed bean:
....
#PostConstruct
public void init() {
category = new Category();
category.setName("Programmatically added ctg");
category.setSlug("programmatically-added-crg");
Set<Subcategory> subcats = new HashSet<>(2);
Subcategory subcat = new Subcategory();
subcat.setName("Subcat one");
subcats.add(subcat);
Subcategory subcat2 = new Subcategory();
subcat2.setName("Subcat to be removed");
subcats.add(subcat2);
Subcategory subcat3 = new Subcategory();
subcat3.setName("The most recent subcat");
subcats.add(subcat3);
category.setSubcats(subcats);
// this initially saves both the cat and the subcats
ctgService.save(category);
categories = ctgService.getAll();
// now I remove one of the three subcats
category.getSubcats().remove(subcat2);
// this is a method belonging to my service (EJB)
ctgService.update(category);
// upon re-fetching, I can see in my DB that the subcat has not been removed
categories = ctgService.getAll();
}
....
I got it to work by changing (in Category entity) #ManyToMany(cascade = CascadeType.ALL) to #ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}).
Indeed, it removes the subcat as desired but... When I take a look at my categories (there's only one in this scenario) - I can see that it somehow has been re-inserted because it now has the cat_id of 2 instead of 1.
Could anyone shed some light on any/both of the issues I'm experiencing?
I think you want 'orpahnremoval' but it's not available on #ManyToMany
How do I delete orphan entities using hibernate and JPA on a many-to-many relationship?

Hibernate One To Many criteria doesn't work

I have the following entities:
#Entity
#Table(name = "author")
public class Author implements Serializable {
private static final long serialVersionUID = 12345L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "author_id")
private int authorId;
#Column(name = "author_bio")
private String authorBio;
#Column(name = "author_email")
private String authorEmail;
#Column(name = "author_favourite_section")
private String authorFavouriteSection;
#Column(name = "author_password")
private String authorPassword;
#Column(name = "author_username")
private String authorUsername;
#OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Blog blog;
#OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Post> posts;
// getters and setters
#Entity
#Table(name = "blog")
public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "blog_id")
private int blogId;
#Column(name = "blog_title")
private String blogTitle;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "blog_author_id", unique = true)
private Author author;
#OneToMany(mappedBy = "blog", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Post> posts;
// getters and setters
#Entity
#Table(name = "post")
public class Post implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "post_id")
private int postId;
#Column(name = "post_subject")
private String postSubject;
#Column(name = "post_body")
private String postBody;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "blog_id")
private Blog blog;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "post_author_id")
private Author author;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tag", joinColumns = {
#JoinColumn(name = "post_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "tag_id",
nullable = false, updatable = false)})
private Set<Tag> tags = new HashSet<Tag>();
// getters and setters
#Entity
#Table(name = "tag")
public class Tag implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id")
private int tagId;
#Column(name = "tag_name")
private String tagName;
#ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY)
private Set<Post> posts = new HashSet<Post>();
// getters and setters
The following data is presented in db:
author-blog-post-tag-AND-post_tag-tables
THE MAIN GOAL TO ACHIEVE IS: Find all authors which have written posts that contain appropriate tags.
I can do it using a SQL query:
SELECT a.author_id, a.author_bio, p.post_id, p.post_subject, t.tag_id, t.tag_name from author a
join blog b
on a.author_id = b.blog_author_id
join post p
on p.post_author_id = a.author_id
join post_tag pt
on p.post_id = pt.post_id
join tag t
on t.tag_id = pt.tag_id
where t.tag_name in ('Football', 'Basketball')
And the correct result is returned with author, filtered posts and tags.
But I need to do it using hibernate.
So using hibernate I want to find all authors which have written posts that contain appropriate tags.
And all those authors with ONLY those posts which contain indicated tags (see above - 'Football', 'Basketball') have to be returned.
I wrote this code:
final DetachedCriteria authorCriteria = DetachedCriteria.forClass(Author.class, "author");
authorCriteria.createAlias("author.posts", "post");
authorCriteria.createAlias("post.tags", "tag");
Criterion football = Restrictions.eq("tag.tagName", "Football");
Criterion basketball = Restrictions.eq("tag.tagName", "Basketball");
authorCriteria.add(Restrictions.or(football, basketball));
authorCriteria
.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
final List<Author> result = (List<Author>)getConfiguredHibernateTemplate().findByCriteria(authorCriteria);
and I expected to receive:
Author(author_id = 54) with only one Post (post_id = 26) and this post containing two tags ('Football' and 'Basketball') as I received it using above SQL query.
But the actual result is that I receive Author(author_id = 54) with ALL HIS POSTS PRESENTED IN DB (ERROR AND PROBLEM HERE !!!) and each post contains all tags which also presented in db.
intellij-idea-debug-result
Hibernate generated the following queries:
select this_.author_id as author_i1_0_2_, this_.author_bio as author_b2_0_2_, this_.author_email as author_e3_0_2_, this_.author_favourite_section as author_f4_0_2_, this_.author_password as author_p5_0_2_, this_.author_username as author_u6_0_2_, post1_.post_id as post_id1_2_0_, post1_.post_author_id as post_aut4_2_0_, post1_.blog_id as blog_id5_2_0_, post1_.post_body as post_bod2_2_0_, post1_.post_subject as post_sub3_2_0_, tags5_.post_id as post_id1_2_, tag2_.tag_id as tag_id2_3_, tag2_.tag_id as tag_id1_4_1_, tag2_.tag_name as tag_name2_4_1_ from author this_ inner join post post1_ on this_.author_id=post1_.post_author_id inner join post_tag tags5_ on post1_.post_id=tags5_.post_id inner join tag tag2_ on tags5_.tag_id=tag2_.tag_id where (tag2_.tag_name=? or tag2_.tag_name=?)
select blog0_.blog_id as blog_id1_1_0_, blog0_.blog_author_id as blog_aut3_1_0_, blog0_.blog_title as blog_tit2_1_0_ from blog blog0_ where blog0_.blog_author_id=?
select posts0_.post_author_id as post_aut4_0_0_, posts0_.post_id as post_id1_2_0_, posts0_.post_id as post_id1_2_1_, posts0_.post_author_id as post_aut4_2_1_, posts0_.blog_id as blog_id5_2_1_, posts0_.post_body as post_bod2_2_1_, posts0_.post_subject as post_sub3_2_1_ from post posts0_ where posts0_.post_author_id=?
How do I achieve the expected and correctly filtered result using hibernate?
You asked for an author which writes a blog about Football or BaketBall:
DetachedCriteria.forClass(Author.class, "author");
It happens that this author also wrote blogs about something else. So you get what you've asked for. In your sql statement you ask for a projection whereas with hibernate you ask the ORM to get the object (author) with its posts collection.
I tried using projection ( authorCriteria.setResultTransformer(CriteriaSpecification.PROJECTION )
final DetachedCriteria authorCriteria = DetachedCriteria.forClass(Author.class, "author");
authorCriteria.createAlias("author.posts", "post");
authorCriteria.createAlias("post.tags", "tag");
final Criterion football = Restrictions.eq("tag.tagName", "Football");
final Criterion basketball = Restrictions.eq("tag.tagName", "Basketball");
authorCriteria.add(Restrictions.or(football, basketball));
authorCriteria.setResultTransformer(CriteriaSpecification.PROJECTION);
final List<Author> result = (List<Author>) getConfiguredHibernateTemplate().findByCriteria(authorCriteria);
and the following result I see in debugger:
Ok that is correct and I can analyze those data and sorted them to Author->List of Posts -> with Tags to specific Post. But this is extra work in code.
I assume maybe hibernate has more elegant way to return filtered data I need or NOT. If no way then I became disappointed of hibernate. Then it is more convenient to use some spring jdbc template or mybatis or something like that.
Using approach "You could then select Post as your root entity" involves extra queries to db. Why we need to do so extra work ? Looks like hibernate is not flexible and useful suffice if it can not run query which I want and return results in way I prefer.

Bidirectional #ManyToOne with join table creates duplicate key

I'm implementing a #ManyToOne bidirectional relationship with a join table using hibernate, but when I'm persisting some data, the hibernate claims that the record in relationship table is being inserted twice, violating the unique constraint, as the error message below shows:
ERROR: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ERROR: duplicate key value violates unique constraint "tillage_sample_pkey"
Detail: Key (id_tillage, id_sample)=(82, 110) already exists.
I have the following tables:
Tillage (id, some other data) (One Tillage can have many samples)
Sample (id, some other data) (One Sample can have one Tillage only)
tillage_sample (id_tillage, id_sample) PK (id_tillage, id_sample)
When I create a Tillage object, I fill with a Sample. In the Sample Object, I point with the Tillage object, creating a "double binding".
I guess that this "double bind" is causing the trouble, as Tillage/Sample relationship is persisted by hibernate when is saving the Tillage and repeating the step when it tries to persist the Tillage inside the Sample (which is the same tillage object).
Here Goes my code, to help you to understand my issue:
Tillage.java
#Entity
#Table(name = "tillage")
public class Tillage implements Serializable {
private static final long serialVersionUID = 3605331584324240290L;
#Id
#GeneratedValue(generator = "tillage_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "tillage_id_seq", sequenceName = "tillage_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
#ManyToOne
#JoinColumn(name = "id_farm")
#JsonBackReference
private Farm farm;
// This relation is the problematic one
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_sample") })
private List<Sample> sampleList;
// Although similar, this one is doing OK
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "tillage_owner",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_owner") })
private List<Owner> ownerList;
// getters & setters
}
Sample.java
#Entity
#Table(name = "sample")
public class Sample implements Serializable {
private static final long serialVersionUID = 7064809078222302493L;
#Id
#GeneratedValue(generator = "sample_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "sample_id_seq", sequenceName = "sample_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
// This completes the relation Tillage-Sample
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_tillage") })
private Tillage tillage = new Tillage();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "sample_sample_item",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_sample_item") })
private List<SampleItem> sampleItemList;
// Getters and Setters
}
SomeService.java
...
#Override
public Tillage toTillage(TillageDTO dto) {
Tillage tillage = new Tillage();
tillage.setName(dto.getNameTillage());
// Fill the samples of the tillage
for(ArrSample sample : dto.getSamples().getArrSample()){
Sample s = new Sample();
s.setName(sample.getName());
// Setting the tillage in the Sample object
s.setTillage(tillage);
// Fill the items of the sample
for(Array arr : sample.getAreas().getArray()){
SampleItem si = new SampleItem();
si.setProduction(Double.parseDouble(arr.getProduction()));
// Double binding between sample and sampleItem
si.setSample(s);
s.getSampleItemList().add(si);
}
// Adding a sample to Tillage
tillage.getSampleList().add(s);
}
return tillage;
}
public void save(TillageDTO dto){
Tillage t = this.toTillage(dto);
// The error occurs when we persist the data
// The entityManager is Autowired by Spring and works in other places
entityManager.persist(tillage);
}
That's not a bidirectional OneToMany. That's too separate unidirectional associations using the same join table.
In a bidirectional association, one side must be the inverse of the other side. For a OneToMany, the One side must be the inverse side:
#OneToMany(mappedBy = "tillage", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<Sample> sampleList;

Categories