JPA. Many to Many creates duplicated rows - java

please help me understand where I was wrong.
I have three table:
Table Wishes
CREATE TABLE WISHES(
wish_id bigint default nextval('wish_id_inc'::regclass),
target text not null,
PRIMARY KEY(wish_id)
)
Table TAGS
CREATE TABLE TAGS(
tag character varying(255) not null,
PRIMARY KEY(tag)
)
Table Wish_tags
CREATE TABLE wish_tags(
wish_tags bigint default nextval('wish_tags_id_inc'::regclass),
wish_id bigint references wishes(wish_id),
tag_id character varying(255) references tags(tag),
PRIMARY KEY(wish_tags)
)
I created two classes for these tables:
#Entity
#Table(name="wishes")
public class Wish implements Serializable{
...
#ManyToMany
#JoinTable(
name="wish_tags",
joinColumns={#JoinColumn(name="wish_id", referencedColumnName="wish_id")},
inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="tag")})
private List<Tag> tags;
...
}
#Entity
#Table(name="tags")
public class Tag implements Serializable{
...
#ManyToMany(mappedBy="tags")
private List<Wish> whishes;
...
}
When i try to create wish with tags, i get duplicate into wish_tags table.
#Transactional
public Wish createWish(List<String> tags){
//em is EntityManager
ArrayList<Tag> ObTags = new ArraList<Tag>();
for(String tagId: tags){
Tag tag = new Tag(tagId);
ObTags.add(em.merge(tag));
}
Wish wish = new Wish(args1,..., ObTags);
em.persist(wish);
}
What i do wrong? Why duplicate is created? Please, help me.

Two tags are saved in DB because one is saved on EntityManager.merge(tag) and the other on cascading EntityManager.persist(wish) to ObTags collection.

Sorry guys, it was my mistake in code. I put the same tags twice into wish.

Related

Why Hibernate is not performing the JOIN on this MANY TO MANY association table using #ManyToMany and #JoinTable annotation?

I am working on a Spring Boot application using Spring Data JPA and Hibernate mapping and I have the following problem.
I have this network table:
CREATE TABLE IF NOT EXISTS public.network
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT network_pkey PRIMARY KEY (id)
)
and this chain table:
CREATE TABLE IF NOT EXISTS public.chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
name character varying(50) COLLATE pg_catalog."default" NOT NULL,
fk_chain_type bigint NOT NULL,
description text COLLATE pg_catalog."default",
CONSTRAINT chain_pkey PRIMARY KEY (id),
CONSTRAINT "chain_to_chain_type_FK" FOREIGN KEY (fk_chain_type)
REFERENCES public.chain_type (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
These 2 tables are related to each other by a MANY TO MANY relationship, implmemented by this network_chain table:
CREATE TABLE IF NOT EXISTS public.network_chain
(
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
fk_network_id bigint NOT NULL,
fk_chain_id bigint NOT NULL,
CONSTRAINT network_chain_pkey PRIMARY KEY (id),
CONSTRAINT chain_id_fk FOREIGN KEY (fk_chain_id)
REFERENCES public.chain (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID,
CONSTRAINT network_id_fk FOREIGN KEY (fk_network_id)
REFERENCES public.network (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
NOT VALID
)
Basically the fk_network_id field represents the id of a specific record into the network table while the fk_chain_id represents the id of a specific record into the chain table.
I mapped these DB tables with the following Hibernate entity classes, first of all I created this Network class mapping the network table:
#Entity
#Table(name = "network")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Network implements Serializable {
private static final long serialVersionUID = -5341425320975462596L;
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(cascade = { CascadeType.MERGE })
#JoinTable(
name = "network_chain",
joinColumns = { #JoinColumn(name = "fk_network_id") },
inverseJoinColumns = { #JoinColumn(name = "fk_chain_id") }
)
Set<Chain> chainList;
}
As you can see it contains the #ManyToMany annotation using the #JoinTable annotation in order to join my network table with my chain table (mapped by the Chain entity class) using the previous network_chain table (implementing the MANY TO MANY relationship).
So in this #JoinTable annotation I am specifying:
The merging table implementing the MANY TO MANY relationship: network_chain.
the two FK on this table that are fk_network_id and fk_chain_id.
Then I have this Spring Data JPA repository class named NetworkRepository:
public interface NetworkRepository extends JpaRepository<Network, Integer> {
/**
* Retrieve a Network object by its ID
* #param id of the network
* #return the retrieve Network object
*/
Network findById(String id);
/**
* Retrieve a Network object by its name
* #param name of the network
* #return a Network object
*/
Network findByName(String name);
/**
* Retrieve the list of all the possible networks
* #return a List<Network> object: the list of the all the networks
*/
List<Network> findAll();
}
Finally I created a JUnit test class containing a method in order to test the previous repository findAll() method, this one:
#SpringBootTest()
#ContextConfiguration(classes = GetUserWsApplication.class)
#TestMethodOrder(OrderAnnotation.class)
public class NetworkTest {
#Autowired
private NetworkRepository networkRepository;
/**
* Retrieve the networks list
*/
#Test
#Order(1)
public void findAllTest() {
List<Network> networksList = this.networkRepository.findAll();
assertTrue(networksList.size() == 5, "It retrieved 5 networks");
Set<Chain> networkChain = networksList.get(0).getChainList();
assertTrue(networkChain != null, "The network chains list are not null");
}
}
The problem is that the findAll() method execute this SQL statement( I can see it into my stacktrace):
Hibernate:
select
network0_.id as id1_6_,
network0_.description as descript2_6_,
network0_.name as name3_6_
from
network network0_
and retrieve the expected List object but as you can see in the following printscreen my chainList field give an error:
It seems that it have not retrieved the chainList from my MANY TO MANY table (infact thre previous Hibernate statement seems not perform any join on the network_chain and then chain tables).
I also tried to directly access to this field by this line (my idea was that maybe Hibernate performed this join when the access to this field is explicitly performed:
Set<Chain> networkChain = networksList.get(0).getChainList();
It seems that this com.sun.jdi.InvocationException exceptions happens when I try to retrieve this field. Basically it seems that the JOINS between the MANY TO MANY table and the on the chain table is never performed.
Why? What is wrong with my code? What am I missing? How can I try to fix it?
This is happening because you have an extra column (id) in the table (network_chain) which is responsible for the many-to-many relationship.
The #JoinTable annotation will support the definition of only 2 fields, usually the ids of the 2 tables. The 2 ids together, will be the primary key for the join table, also known as a composite primary key.
In your case, you will have to create an entity to represent the table network_chain.
#Entity
#Table(name = "network_chain")
public class NetworkChain {
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Network network;
#ManyToOne
private Chain chain;
}
public class Network implements Serializable {
//somewhere in the code
#OneToMany
List<NetworkChain> networkChains;
}
public class Chain implements Serializable {
//somewhere in the code
#OneToMany
List<NetworkChain> networkChains;
}
Now, the entity NetworkChain has the extra column id as the primary key and hibernate will do the proper joins to fetch the data.
Out of topic but not sure why there is a javadoc for the findAll() method which is usually part of the JpaRepository and weird findById as well..

JPA eclipselink persist entity that has no primary key but two foreign keys

I am very new to java EE and self-learning it, so please be patient with me. So far I have 3 simple tables in my database:
Sales
Products
Sale_id
Product_id
Sale_details
Saleid_fk
Productid_fk (fk)
Quantity_sold
How do I persist a new sale_details without creating a sale_details entity. Because Java wont let me name sale_details as #Entity without a primary key.
this current code gives me an error stating i need to declare a primary key. I tried using Idclass based on the answers on stackoverflow but it doesn't do anything.
#Entity
#Table(name="sale_details")
#IdClass(value = SaleDetails.class)
public class SaleDetails implements Serializable {
#Column(name = "saleid_fk")
private int saleid_fk;
#Column(name = "productid_fk")
private String productid_fk;
#Column(name = "quantity_sold")
private int quantity_sold;
what am I missing here?
You can just set a primary id as autoincrement and not use it.

Hibernate mapping of similar columns

if I create a table like:
CREATE TABLE IF NOT EXISTS `user`(
`id` INT NOT NULL AUTO_INCREMENT,
`email_1_type` INT NULL,
`email_1` VARCHAR(255) NULL,
`email_2_type` INT NULL,
`email_2` VARCHAR(255) NULL,
`email_3_type` INT NULL,
`email_3` VARCHAR(255) NULL,
PRIMARY KEY (`id`)
)
can I map email columns as list? Like:
public class Email{
private int type;
private String email;
// getter & setter
}
#Entity
#Table(name = "info_req_chat_time_detail")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private List<Email> emails;
// getter & setter
}
Don't do that. Java collections types are used to map relationships to other tables that need foreign keys.
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/collections.html
As a requirement persistent collection-valued fields must be declared
as an interface type (see Example 7.2, “Collection mapping using
#OneToMany and #JoinColumn”). The actual interface might be
java.util.Set, java.util.Collection, java.util.List, java.util.Map,
java.util.SortedSet, java.util.SortedMap or anything you like
("anything you like" means you will have to write an implementation of
org.hibernate.usertype.UserCollectionType).
It actually would be good design to have another table of just Phones containing of id(FK), emailType, emailValue. Then you could do what you're listing.
But if you're trying to keep it simple and know for sure about 3 phones, don't do that. You never know when you'll want to modify or augment just one of the SQL columns. If you need to use a List in the Java class, build a private List<Email> getAllEmailsAsList() method collecting all the individual email fields and packaging them into a list for you.
There's nothing wrong with having the 6 extra member fields in your #Entity class to handle this table's creation.

How to map two tables to one entity using foreign-key?

I have a problem very similar to this: How do I join tables on non-primary key columns in secondary tables?
But I'm not sure if I can apply the same solution.
I have two tables like these:
CREATE TABLE CUSTOMER
(
CUSTOMER_ID INTEGER NOT NULL,
DETAIL_ID INTEGER NOT NULL,
PRIMARY KEY( CUSTOMER_ID ),
CONSTRAINT cust_fk FOREIGN KEY( DETAIL_ID ) REFERENCES DETAILS( DETAIL_ID )
)
CREATE TABLE DETAILS
(
DETAIL_ID INTEGER NOT NULL,
OTHER INTEGER NOT NULL,
PRIMARY KEY( DETAIL_ID )
)
I'd like to map these tables to a single class called Customer, so I have:
#Entity
#Table(name = "CUSTOMERS")
#SecondaryTable(name = "DETAILS", pkJoinColumns=#PrimaryKeyJoinColumn(name="DETAIL_ID"))
public class Customer {
#Id
#GeneratedValue
#Column(name = "CUSTOMER_ID")
private Integer id;
#Column(table = "DETAILS", name = "OTHER")
private Integer notes;
// ...
}
but this works only if DETAIL_ID matches CUSTOMER_ID in the primary table.
So my question is: how can i use a foreign-key field in my primary table to join on the primary-key of the secondary table?
UPDATE
I tried to set:
#SecondaryTable(name = "DETAILS", pkJoinColumns=#PrimaryKeyJoinColumn(name="DETAIL_ID", referencedColumnName="DETAIL_ID"))
but when I run the application I get this exception:
org.hibernate.MappingException: Unable to find column with logical name: DETAIL_ID in org.hibernate.mapping.Table(CUSTOMERS) and its related supertables and secondary tables
For anyone looking for an answer to this, using #SecondaryTable is not the way to join two tables with non-primary key columns, because Hibernate will try to assosiate the two tables by their primary keys by default; you have to use #OneToMany review http://viralpatel.net/blogs/hibernate-one-to-many-annotation-tutorial/ for a solution, here's a code snippet in case that url stops working:
Customer Class:
#Entity
#Table(name="CUSTOMERS")
public class Customer {
#Id
#GeneratedValue
#Column(name="CUSTOMER_ID")
private Integer id;
#ManyToOne
#JoinColumn(name="DETAIL_ID")
private Details details;
// Getter and Setter methods...
}
Details Class:
#Entity
#Table(name="DETAILS")
public class Details {
#Id
#GeneratedValue
#Column(name="DETAIL_ID")
private int detailId;
#Column(name="OTHER")
private String other;
#OneToMany(mappedBy="details")
private Set<Customer> customers;
// Getter and Setter methods...
}
This is easily accessible through hibernate with the following code:
Session session = HibernateUtil.getSessionFactory().openSession();
Query query = session.createQuery("select id, details.other from Customer");
I hope this helps anyone out there spending hours searching for a way to achieve this like I did.
You can use the referenceColumnName attribute of the #PrimaryKeyJoinColumn annotation to define the join column to the referenced table. In fact, by combining use of name/referencedColumnName you can join on arbitrary on both sides, with the constraint that if duplicates are found your ORM provider will throw an exception.

Hibernate #ElementCollection - Better solution needed

I'm using Hibernate 3.5.a-Final as ORM-Layer in a web application. I've got several Beans with the same code-sniplet wich makes me think that this design isn't the best one around. But I can't figure out how to implement a better one in hibernate.
Requirements
Several classes need to contain localized descriptions in multiple locales
These need to be persisted into the db
They have to be searchable by substring for all locales (show up if the seachstring is a substring of any description)
Localized descriptions should be queryable without loading the master-object (by master-object-id, -type and locale)
Current solution (doesn't solve the last requirement)
Each class contains a HashMap annotated as
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name = "localized[X]Descriptions", joinColumns = #JoinColumn(name = "id"))
#MapKeyJoinColumn(name = "locale")
public Map<Locale, String> getLocalizedDescriptions() {
return localizedDescriptions;
}
[X] beeing the name of the class
For Each class the is an additional table (generated by hibernate)
create table localized[X]Descriptions (
id integer not null,
localizedDescriptions varchar(255),
localizedDescriptions_KEY varchar(255),
primary key (id, localizedDescriptions_KEY)
)
For some reason the #MapKeyJoinColumn gets ignored...
What I'd prefer would be a single table like this:
create table localizedDescriptions (
class varchar(255) not null,
id integer not null,
locale varchar(50) not null,
description varchar(255) not null,
primary key (class, id, locale)
)
It would be a big plus if the implementation would be queryable using the criteria-api (which isn't compatible to #ElementCollections as far as I know).
But I can't figure out how to implement this. Any pointers would be very welcome
I found my own solution...
I just use
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="masterClass", discriminatorType=DiscriminatorType.INTEGER)
#Table(name="localizedDescriptions")
public class LocalizedDescriptions{
private Integer id;
private Locale locale;
private String description;
[Getters, Setters]
}
as my parent-class for all localized descriptions and extend it like
#Entity
public class LocalizedSomeDescription extends LocalizedDescription {
private Some master;
/**
* #return the master
*/
#ManyToOne
public Some getMaster() {
return master;
}
Which gets used like this:
#Table
#Entity
public class Some {
private Map<Locale, LocalizedSomeDescription> names = new HashMap<Locale, LocalizedSomeDescription>();
#OneToMany
#JoinColumn(name="master_id")
#MapKeyColumn(name="locale")
public Map<Locale, LocalizedSomeDescription> getDescriptions() {
return descriptions;
}
}
This results in something very similar to my intended table design
create table localizedDescriptionss (
masterClass integer not null,
id integer not null auto_increment,
locale varchar(255),
description varchar(255),
master_id integer,
primary key (id)
)
using mappedBy="master" in all subclasses might seem like an abuse of hibernate inheritance but all other solutions would include one row per subclass which would be null in every other, which seems to me like a very bad table design. I still have to find out what's the 'sensible default' for a discriminatorType=DiscriminatorType.INTEGER and if I need to override that default.

Categories