HQL Many to Many JOIN with extra columns - mysql

I have a many to many relationship in Hibernate with additional colums so I have an extra java class for the model and another class with the primary key... now in HQL I need a query that retrieve those data but I have problems with Join condition.
This is my First class:
#Entity
#Table(name = "Firsts")
public class First {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(mappedBy = "primaryKey.second")
private List<FirstsSeconds> seconds = new LinkedList<>();
#Column(name="description")
private String description;
...
}
The Second class:
#Entity
#Table(name="Seconds")
public class Second {
#Id
private String code;
#OneToMany(mappedBy = "primaryKey.first")
private List<FirtsSeconds> firsts = new LinkedList<>();
...
}
And the table manyToMany with additional columns:
#Entity
#Table(name = "firsts_seconds")
#AssociationOverrides({ #AssociationOverride(name = "primaryKey.first", joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "primaryKey.second", joinColumns = #JoinColumn(name = "code")) })
public class FirstsSeconds{
#EmbeddedId
private FirstsSecondsId primaryKey = new FirstsSecondsId();
#Column(name = "extra", nullable = false)
private String extra;
...
}
So the id class:
#Embeddable
public class FirstsSecondsId {
#ManyToOne
private First first;
#ManyToOne
private Second second;
...
}
Finally to get HQL result I create a new class with the field that I want:
public class NewObject
public CargoOrder(String firstDescription, String fsExtra) {
this.firstDescription = firstDescription;
this.fsExtra = fsExtra;
}
...
First of all I want First descrption and extra from FirstsSecond, so this is my query with JOIN fr.seconds as fs:
#Query("SELECT new com.mypackage.NewObject("
+ "fr.description as firstDescription, fs.extra as fsExtra) "
+ "FROM First as fr"
+ "JOIN fr.seconds as fs")
public List<NewObject> findManyToMany();
But I have no results :(... in this case I have to specify the where condition?
#Query("SELECT new com.mypackage.NewObject("
+ "fr.description as firstDescription, fs.extra as fsExtra) "
+ "FROM First as fr"
+ "JOIN fr.seconds as fs WHERE fr.first = fs.primaryKey.first")
public List<NewObject> findManyToMany();
Thats not compile on JOIN fr.seconds as fs WHERE fr.first = fs.primaryKey.first...
Kind regards.

Solved... debugging with spring.jpa.show_sql = true in application-properties I see that there was a bad matching in the join condition, I mapped wrong keys:
In First class it's primaryKey.first (not second):
#OneToMany(mappedBy = "primaryKey.first")
private List<FirstsSeconds> seconds = new LinkedList<>();
And in Second primaryKey.second (not first):
#OneToMany(mappedBy = "primaryKey.second")
private List<FirstsSeconds> firsts = new LinkedList<>();

Related

JPA/Hibernate - How implement soft delete in a #OneToMany relationship

I'm trying implement soft delete in a #OneToMany relationship in an academic project. I need to mark as "deleted" a child entity in the following scenarios:
When the parent entity is deleted (soft deleted as well).
When the child entity is no longer referenced by his parent.
I've achieved this requirement using the #SqlDelete annotation, and the CascadeType.all / orphanRemoval = true options on the #OneToMany side as follow:
Parent
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql = "update Discount set deleted = true where id = ?")
#Where(clause = "deleted = false")
public class Discount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
/// More properties ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "discountId", nullable = false)
private List<PetrolStationDiscount> petrolStationDiscounts = new ArrayList<>();
public Discount() {}
// Getters and setters...
public List<PetrolStationDiscount> getPetrolStationDiscounts() {
return petrolStationDiscounts;
}
public void setPetrolStationDiscounts(List<PetrolStationDiscount> petrolStationDiscounts) {
this.petrolStationDiscounts = petrolStationDiscounts;
}
// HashCode & Equals methods
Child Entity
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql="UPDATE PetrolStationDiscount SET deleted = true WHERE id = ?")
public class PetrolStationDiscount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name = "petrolStationId")
private PetrolStation petrolStation;
#Column(insertable = false, updatable = false)
private String discountId;
// Getters & Setters -- HashCode & Equals methods
BaseDAO
#MappedSuperclass
public abstract class BaseDao {
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date updatedAt;
#Column(name = "deleted", nullable = false)
private boolean deleted = false;
// Getters and Setters
}
This works fine, but I'm not comfortable mixing the sql statement straight into the entities, so I've tried the approach I found in the following thread:
How to soft delete parent and child together (cascade) using spring boot with jpa
Following the thread, I've created a BaseRepository overriding the CrudRepository methods "delete" and "deleteById", but it doesn't work:
When the parent entity is deleted, only the parent deleted field is saved as true.
When a child entity is no longer referenced by his parent, if orphanRemoval is set to true, the child entity is hard deleted, and if false, nothing happened.
#NoRepositoryBean
public interface BaseRepository<T extends BaseDao> extends CrudRepository<T, Serializable> {
#Override
#Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
#Transactional
#Modifying
void deleteById(Serializable id);
#Override
#Transactional
default void delete(T entity) {
deleteById(entity.getId());
}
¿Someone could help me?
A lot of thanks!

JPA soft delete with #SqlDelete causes SQL error: Parameter index out of range (2 > number of parameters, which is 1)

I have Basket and BasketItem entities that both extend BaseEntity as below:
#MappedSuperclass
public class BaseEntity {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "deleted", nullable = false)
private Boolean deleted = false;
#Version #Column(nullable = false)
private Long version;
}
#Entity
#Where(clause = "deleted = false")
#SQLDelete(sql = "UPDATE basket SET deleted=true WHERE id=?")
public class Basket extends BaseEntity {
#OneToMany(mappedBy = "basket", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<BasketItem> items = new ArrayList<>();
}
#Entity
#Where(clause = "deleted = false")
#SQLDelete(sql = "UPDATE basketItem SET deleted=true WHERE id=?")
public class BasketItem extends BaseEntity {
#ManyToOne(optional = false)
#JoinColumn(name = "basket_id", nullable = false)
private Basket basket;
}
Now when I want to delete a BasketItem from a Basket, I just call remove(basketItem) on basket items list and save the Basket instance:
#Service
public class DeleteBasketItemUseCase {
#Transactional
public BasketOutput execute(final Long itemId, final Long basketId) {
// loading and checks omitted
basket.getItems().remove(basketItem);
basket = basketRepository.save(basket);
}
}
Deleting basketItem generates MySQL error:
java.sql.SQLException: Parameter index out of range (2 > number of parameters, which is 1)
Generated SQL in log is:
UPDATE
basketItem
SET
deleted=true
WHERE
id = ?
I am using
Spring Boot 2.2.6.RELEASE
MySQL 8.0
Hibernate 5.4.12.Final
mysql-connector-java-8.0.19
I update #SQLDelete and add and version=? AT the end of annotation:
#SQLDelete(sql = "UPDATE basket SET deleted=true WHERE id=? and version=?")
#SQLDelete(sql = "UPDATE basket_item SET deleted=true WHERE id=? and version=?")
Note: Also I've changed database tables names!

JPA mapping for one-to-many collection of shared data, with user specific values

I have a User model that contains a list of achievements
#Table(name = "user")
#Entity
#NamedEntityGraph(name = "User.achievements",
attributeNodes={
#NamedAttributeNode("achievements")
})
#Data
public class User {
#Id
#NotNull
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ElementCollection(fetch = FetchType.LAZY, targetClass = Achievement.class)
private List<Achievement> achievements = new ArrayList<>();
}
Here's the achievement model
#Entity
#Data
#Table(name = "achievement")
public class Achievement {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String achievementId;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "achieved", columnDefinition="BOOLEAN DEFAULT false", nullable = false)
private boolean achieved = false;
user_achievements table generated from #ElementCollection mapping, which atm only contains user and achievement foreign keys
I am looking to move the boolean achieved value to the user_achievements table, ideally without having to create a separate model User_Achievements
I am fairly new to using Jpa, but i feel like this scenario is too basic so there must be a straight forward way to do that i cant seem to locate it
#Entity
class UserAchievement {
#EmbeddableId
UserAchievementId id;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="user_username", insertable=false, updatable=false)
User user;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="achivement_achivement_id", insertable=false, updatable=false)
Achivement achivement;
// and other fields
}
class User {
// ...
#OneToMany(mappedBy="user")
List<UserAchievement> userAchievements;
}
and you need to define UserAchievementId

Can I use #Where annotation along with #ManytoOne association?

EER Diagram
I am not an expert in Spring, JPA, Hibernate or MySql.
However I am using all for a web service supporting RESTful calls.
I am building a store management app backend with Spring.
My entities at this point of time are StoreModel, StoreUserModel, StoreUserRoleModel and StoreUserAuthModel.
I have setup bidirectional relationships(OneToMany and ManyToOne) between
StoreModel - StoreUserAuthModel,
StoreUserMode - StoreUserAuthModel and
StoreUserRoleMode - StoreUserAuthModel.
I dont want the foreign key constraint though there are foreign key fields storeid, roleid and userid in StoreUserAuthModel.
Now All the four tables have isdeleted column to implement soft delete.
I am lazy fetching the associations. However I dont want the softdeleted values whenever i query the associations.
I would like to know if I can use #Where annotation along with the #ManyToOne annotation in the StoreUserAuthModel entity?
The issue is different from How to use #Where in Hibernate because my problem is with ManyToOne annotation whereas I have used the where annotation with OneToMany
#Entity
#Table(name = "store")
public class StoreModel {
#NotBlank
private String name;
#NotBlank
private String address;
#NotBlank
private String city;
#NotBlank
private String phone;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
#NotBlank
private String name;
#NotBlank
private Integer rolehierarchy;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuser")
public class StoreUserModel{
#NotBlank
#Column(unique = true)
private String username;
#Email
#Column(unique = true)
private String useremail;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
List<StoreUserAuthModel> userAuthList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuserauth",
uniqueConstraints = #UniqueConstraint(columnNames = {"storeid", "roleid", "userid"}))
public class StoreUserAuthModel {
#NotNull
Long storeid;
#NotNull
Long roleid;
#NotNull
Long userid;
// Using #where to filter out the soft deleted storeuser
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreUserModel storeuser;
// Using #where to filter out the soft deleted store
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreModel store;
// Using #where to filter out the soft deleted role
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreRoleModel role;
...
}
// In the controller, Following code shows how I plan to use
Optional<StoreUserModel> aUser = storeUserRepository.findByUseremailAndIsdeleted(zUserMail), 0);
if(aUser.isPresent()) {
// The user was found!!!
// Testing...
// Getting the User Auth List (that will filter out the soft deleted auths)
List<StoreUserAuthModel> authList = aUser.get().getUserAuthList();
for(StoreUserAuthModel auth :authList) {
StoreModel store = auth.getStore();
// here both soft deleted store as well as normal stores are shown.
// ie where clause on store relation is not working!!
logger.debug("Store is "+store.getName());
}
}
...
Now all the store rows matching the id are in the list.
The expected result should apply where clause too
I turned on logging for hibernate 5.3.9
There is no where clause when it fires the select query
The #Where annotation has no effect on ToOne relationships. But instead of adding #Where to the reference you can use #Where on the Entity:
#Where(clause="isdeleted = 0")
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
That way no deleted entities of StoreRoleModel will be loaded by Hibernate.

com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column in 'field list' using Criteria and mapping annotation

i have criterias to access result by
First Hibernate Dao is
AnswerText answersText = questionManager.getAnswerTextByAnswerIdAndLanguageId(answers.getAnswerId(), 1L);
#Override
public AnswerText getAnswerTextByAnswerIdAndLanguageId(Number answerId,Number languageId) {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(AnswerText.class);
criteria.add(Restrictions.eq("answer.answerId", answerId));
criteria.add(Restrictions.eq("languageId", languageId));
List<AnswerText> results = criteria.list();
return (results !=null && !results.isEmpty()? results.get(0): null);
}
Answers.java
#Entity
#Table(name = "ANSWERS")
#Cacheable
#JsonIgnoreProperties(ignoreUnknown = true)
public class Answer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ANSWER_ID")
private Long answerId;
#Column(name = "QUESTION_ID")
private Long questionId;
#Column(name = "DATE_CREATED")
private Timestamp dateCreated;
#Column(name = "CREATED_BY_ID")
private Long creatorId;
#Column(name = "DATE_MODIFIED")
private Timestamp dateModified;
#Column(name = "MODIFIED_BY_ID")
private Long modifierId;
#OneToMany(fetch = FetchType.EAGER,mappedBy = "answer" )
private Set<AnswerText> answerText = new HashSet<AnswerText>();
//getters and setters
AnswerText.java
#Entity
#Table(name = "ANSWERTEXT")
#Cacheable
#JsonIgnoreProperties(ignoreUnknown = true)
public class AnswerText {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ANSWER_TEXT_ID")
private Long answerTextId;
#ManyToOne
#JoinColumn(name="answerId", insertable=false, updatable=false,
nullable=false)
private Answer answer;
#Column(name = "ANSWER_TEXT")
private String answerText;
#Column(name = "LANGUAGE_ID")
private Long languageId;
//getters and setters
When i access the to fetch resultset, it shows below error
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'answertext2_.answerId' in 'field list'
Then i changed to below in
AnswerText.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ANSWER_ID", nullable = false)
private Answer answer;
Answers.java
#OneToMany(fetch = FetchType.EAGER,mappedBy = "answer" )
#Fetch(FetchMode.JOIN)
private Set<AnswerText> answerText = new HashSet<AnswerText>();
This produce no error But fetch results twice on calling
Second HibernateDao call is
List<Answer> answerList = questionManager.getAnswersByQuestionId(Long.parseLong("2"));
System.out.println("answerList :"+answerList1.size());
#Override
public ArrayList<Answer> getAnswersByQuestionId(Number questionId) {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Answer.class);
criteria.add(Restrictions.eq("questionId", questionId));
ArrayList<Answer> answerList = (ArrayList) criteria.list();
return answerList;
}
Can Please anyone point me what is going wrong here. PLease help me.
You have done right changing the JoinColumn-name.
Your multiple results in my opinion is bound to:
FetchType.EAGER and the selection of
FetchMode JOIN As indicated you’ll have to worry about duplicated
results. On the other hand JOIN creates the least amount of queries.
In a high latency environment a single JOIN could be considerable
faster then multiple SELECTS. Keep in mind that joining too much data
could put a strain on the database.
from http://www.solidsyntax.be/2013/10/17/fetching-collections-hibernate/
With FetchMode.SELECT you would get the result like you want to have it.