Hibernate Annotations : Many-to-Many relationship using Join table having common attribute in PK - many-to-many

Is it possible to define a many to many relationship between two tables using a join table which has composite key, having a common attribute between the two tables?
Let's say, we have a table structure like below and am trying to define association between kit and business_product using kit_business_product table. Here are the table definitions (simplified for this question).
Table 1 : kit
PK1 : kit_id, business_id
Table 2 (Join Table) : kit_business_product
PK2 : kit_id, business_id, manufacturer_id, product_id, name
Table 3 : business_product
PK3 : business_id, manufacturer_id, product_id, name
Here are the java classes for these tables (simplified for this question).
Kit.java
#Entity
#Table (name= "kit")
public class Kit
{
#EmbeddedId
private KitId id;
#ManyToMany
#JoinTable(name = "kit_business_product",
joinColumns = {#JoinColumn(name = "kit_id", referencedColumnName = "kit_id", nullable = false, insertable = false, updatable = false),
#JoinColumn(name = "business_id", referencedColumnName = "business_id", nullable = false, insertable = false, updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "business_id", referencedColumnName = "business_id", nullable = false),
#JoinColumn(name = "manufacturer_id", referencedColumnName = "manufacturer_id", nullable = false),
#JoinColumn(name = "product_id", referencedColumnName = "product_id", nullable = false),
#JoinColumn(name = "name", referencedColumnName = "name", nullable = false)
})
private Set<BusinessProduct> businessProducts = new HashSet<BusinessProduct>();
}
KitId.java
#Embeddable
public class KitId
{
#Column (name = "kit_id")
private String kitId;
#Column (name = "business_id")
private long businessId;
}
BusinessProduct.java
#Entity
#Table(name = "business_product")
public class BusinessProduct
{
#EmbeddedId
private BusinessProductId id;
}
BusinessProductId.java
#Embeddable
public class BusinessProductId
{
#Column(name = "business_id")
private long businessId;
#Column(name = "manufacturer_id")
private long manufacturerId;
#Column(name = "product_id")
private String productId;
#Column(name = "name")
private String name;
}
With this code, I get the below given error.
[RMI TCP Connection(3)-127.0.0.1] 2017-03-17 14:17:54,767 ERROR com.common.DatabaseSession [] Repeated column in mapping for collection: com.hibernate.Kit.businessProducts column: business_id
org.hibernate.MappingException: Repeated column in mapping for collection: com.hibernate.Kit.businessProducts column: business_id
at org.hibernate.mapping.Collection.checkColumnDuplication(Collection.java:341) ~[hibernate3.jar:3.6.10.Final]
at org.hibernate.mapping.Collection.checkColumnDuplication(Collection.java:364) ~[hibernate3.jar:3.6.10.Final]
What would be the correct annotation for such a scenario? Any insight would be greatly appreciated. Thanks!

Related

Get children for any level in tree structure but without theirs children using Hibernate?

I have a Folder entity in Hibernate, like so:
#Entity
#Table(name = "folders")
public class Folder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "folder_id", unique = true, nullable = false)
private int id;
#NonNull
#Column(name = "name", length = 100, unique = true, nullable = false)
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
#Column(name = "sub_folders")
private Set<Folder> childFolders = new HashSet<>();
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonIgnore
#JoinColumn(name = "parent", referencedColumnName = "folder_id", nullable = true)
private Folder parent;
public Folder() {
}
}
I'm trying to write a finder method or custom query which will do what I wrote in the subject.
So if I send a request going like folders/{parent_folder_id}, let's say value being 1, I should get objects 4 and 5, but without their children, so not including 6,7,8 and 9.
Ideally, hibernate query would be preferred. If not, any sql language is also fine. I'll try to tumble it up to hibernate.
This is what I got, I still get children...
#Query(value = "Select * from folders f where f.parent = ?1 ", nativeQuery = true)
Set<Folder> getFolders(int folder_id);
I think this should work:
make the default fetchtype lazy:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
#Column(name = "sub_folders")
private Set<Folder> childFolders = new HashSet<>();
Use a JOIN FETCH in order to eagerly fetch the relationships you want.
SELECT f FROM folders f JOIN FETCH f.childFolders
You probably can achieve something similar with entity graphs but I'm not sure about their interaction with queries.
I got what I need with following query:
#Query(value = "Select folder_id, name, parent From folders f Where f.parent = ?1", nativeQuery = true)
This will give me just name of the folder and its Id.

Failed to convert from type [java.lang.Object[]] to type [#org.springframework.data.jpa.repository.Query

I have spring boot project using JPA/Hibernate, MySQL. I have three dao classes that have a many to many relationship.
The Poko classes look like this
Product
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false, columnDefinition = "integer")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "price")
private Double price;
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(
name = "product_extra",
joinColumns = #JoinColumn(name="product_id"),
inverseJoinColumns = #JoinColumn(name="extra_id")
)
private List<Extra> extras = new ArrayList<>();
//constructor getters and setters
}
ProductExtra
#Entity
#Table(name = "product_extra")
public class ProductExtra {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false, columnDefinition = "integer")
private Integer id;
#Column(name = "product_id")
private Integer productId;
#Column(name = "extra_id")
private Integer extraId;
//constructor getters and setter
}
Extra
#Entity
#Table(name = "extra")
public class Extra {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false, columnDefinition = "integer")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "price")
private Double price;
#ManyToMany(mappedBy = "extras")
private List<Product> products = new ArrayList<>();
//constructor getters and setters
}
The Extra repository with the query
public interface ExtraRepository extends JpaRepository<Extra, Integer> {
#Query("SELECT e.id, e.name, e.price FROM Extra e INNER JOIN ProductExtra pe ON e.id = pe.extraId WHERE pe.productId = ?1")
List<Extra> findExtraById(Integer productId);
}
The mapping in my controller
#GetMapping("/product/{productId}")
public List<Extra>getExtraById(#PathVariable("productId") final Integer productId){
return extraRepository.findExtraById(productId);
}
I am trying to make a many to many query to select The extras in each product, i am however getting this error Failed to convert from type [java.lang.Object[]] to type [#org.springframework.data.jpa.repository.Query the error message surprisingly also contains the results i want. Not sure what am doing wrong
Remove the SELECT clause:
#Query("FROM Extra join e.productExtra WHERE pe.productId = ?1")
Also keep in mind, that you not write an SQL Query, You work on Object, so for join you use the mapped property

could not execute statement; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

I try to insert some data by posting JSON, but I dont know why, it returns me an error saying that Field 'id_accessoire' could not be empty (null).
When I send my JSON with Advanced Rest Client, the field id_accessoire is not empty.
First, this i my entity (file Affaire.java) , with all the fields like they are in my database :
#Entity
#Data
#Table(name = "affaire")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Affaire implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", insertable = false, nullable = false)
private Integer id;
#Column(name = "sn", nullable = false)
private String sn;
#Column(name = "zc")
private String zc;
#Column(name = "certificat")
private String certificat;
#Column(name = "etat", nullable = false)
private Boolean etat;
#Column(name = "finEval", nullable = false)
private Date finEval;
#Column(name = "finRep", nullable = false)
private Date finRep;
#Column(name = "limiteCalendaire", nullable = false)
private String limiteCalendaire;
#Column(name = "date_lancement", nullable = false)
private Date dateLancement;
#Column(name = "accordClient", nullable = false)
private boolean accordClient;
#Column(name="idAccessoire", insertable = false, updatable = false)
private Integer idAccessoire;
#Column(name="idFlux", insertable = false, nullable = false, updatable = false)
private Integer idFlux;
#Column(name="idReparation", insertable = false, nullable = false, updatable = false)
private Integer idReparation;
#Column(name="idVariante", insertable = false, nullable = false, updatable = false)
private Integer idVariante;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idAccessoire", nullable = false)
#JsonBackReference(value = "affaireAccess")
private Accessoire accessoire;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idFlux", nullable = false)
#JsonBackReference(value = "affaireFlux")
private Flux flux;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idReparation", nullable = false)
#JsonBackReference(value = "affaireRep")
private Reparation reparation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idVariante", nullable = false)
#JsonBackReference(value = "affaireVariante")
private Variante variante;
#OneToMany(mappedBy = "affaire", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
//#JsonManagedReference(value = "affecterAffaire")
#ToString.Exclude
private List<Affecter> affecters;
}
And finally, this is the JSON that I try to post, but it's not working :
{
"sn":"4444AAA",
"zc":null,
"certificat":null,
"etat" : false,
"finEval": "2019-08-25",
"finRep": "2019-09-14",
"limiteCalendaire":"10R28",
"dateLancement":"2019-07-19",
"accordClient" : false,
"idAccessoire" : 1,
"idFlux": 1,
"idReparation": 2,
"idVariante" : 7,
}
The error is that the field "id_accessoire_ could not be empty (null)
From you entity class, I can see that idAccessoire column in not insertable not updatable.
#Column(name="idAccessoire", insertable = false, updatable = false)
private Integer idAccessoire;
This means that irrespective of which value you set in code, it will be ignored.
Now, If idAccessoire is defined as not nullable field in your table configuration this error is thrown.
1) Do you really want this field to be not insertable or updatable?
2) If you must specify this column as non null in database, try to specify a default value, if it suites your business logic.

Data lost when trying to edit record

I'm currently having this issue: I have 3 tables: users, roles and users_roles as in the picture below:
whenever I edit any of the record in users table, the record of that user in the users_roles table will be lost.
For example, I changed the username of the user which holds the userId = 2, then in the users_roles table, the row of userId = 2 will be lost.
Anybody has any ideas of this problem? I'm using Spring with Hibernate
*UPDATE
In my Role.java
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "roles")
private List<User> users;
In my User.java
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "users_roles", joinColumns = #JoinColumn(name = "userId", nullable = false) , inverseJoinColumns = #JoinColumn(name = "roleId", nullable = false) )
private List<Role> roles;
And in my UsersRoles.java
#Id
#Column(name="userId")
private int userId;
#Id
#Column(name="roleId")
private int roleId;
This is the DAO implementation method I used for Edit
#Override
public void edit(User user) {
session.getCurrentSession().saveOrUpdate(user);
}
P/S: this not only happens when I edit with my web-app, but also happens when I edit directly in MySQL environment. I don't know...

Converting SQL query to JPA NamedQuery

I'm trying to implement a Keyword search functionality that returns a List of Keyword entities based on a field text match.
Right now, the query
select * from photo_keywords pk
inner join keywords k on pk.photo_id = k.keyword_id
inner join photos p on pk.keyword_id = p.photo_id
where k.keyword LIKE "%$SOME_SEARCH_VALUE%";
returns all matching photos for a given keyword search. I'd like to have this adapted to a #NamedQuery with the following Entity objects:
#Entity
#Table(name = "keywords")
public class Keyword implements Serializable{
#Id
#Column(name = "keyword_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String keyword;
#ManyToMany(mappedBy = "keywords")
private List<Photo> photos;
//getters and setters
}
and
#Entity
#Table(name = "photos")
public class Photo implements Serializable{
#Id
#Column(name = "photo_id", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "photo_name", nullable = false)
private String photoName;
#Column(name = "photo_path", nullable = false)
private String photoPath;
#Column(name = "upload_date", nullable = false)
private Date uploadDate;
#Column(name = "view_count", nullable = false)
private int viewCount;
#Column(name = "capture_date", nullable = false)
private Date captureDate;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "photo_metadata")
#MapKeyColumn(name = "metadata_name")
#Column(name = "metadata_value")
private Map<String, String> photoMetadata;
#ManyToMany
#JoinTable(name = "photo_keywords",
joinColumns = #JoinColumn(name = "keyword_id"),
inverseJoinColumns = #JoinColumn(name = "photo_id"))
public List<Keyword> keywords;
//getters and setters
}
This creates a join table photo_keywords, rather than a JoinColumn.
What I've tried so far with the Keyword entity:
#NamedQueries({
#NamedQuery(
name = "findKeywordByName",
query = "SELECT keyword from Keyword k WHERE k.keyword = :keyword"
)
})
which is executed via
public Keyword findKeywordByString(String keyword){
Keyword thisKeyword;
Query queryKeywordExistsByName = getEntityManager().createNamedQuery("findKeywordByName");
queryKeywordExistsByName.setParameter("keyword", keyword);
try {
thisKeyword = new Keyword((String) queryKeywordExistsByName.getSingleResult());
} catch (NoResultException e){
thisKeyword = null;
}
return thisKeyword;
}
This returns the Keyword, but with the photos property being null. This is to be expected, since I'm only selecting the keyword property. How can I adapt the SQL query above to a #NamedQuery?