OneToMany & ManyToOne in Hibernate Mapping - mysql

public class ClassYear {
private Set<Examination> examination = new HashSet<Examination>();
#Id
#Column(name = "class_id")
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#DocumentId
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER)
#JoinColumn(name="class_id")
public Set<Examination> getExamination() {
return examination;
}
public void setExamination(Set<Examination> examination) {
this.examination = examination;
}
}
public class Examination {
private ClassYear classYear;
#ManyToOne(cascade={CascadeType.ALL},fetch=FetchType.EAGER)
#JoinColumn(name="class_id")
public ClassYear getClassYear() {
return classYear;
}
public void setClassYear(ClassYear classYear) {
this.classYear = classYear;
}
}
So here, Examination table refers the class_id column of ClassYear table. Now I need the class_id column to be used on few other tables. How should I do it..? It's One to many relationship concept but I need that class_id to be used on many tables (say like HomeWork table). I searched the Internet and found explanations for one to many & many to one concepts, but I can't find exactly how to do it for multiple tables with a particular column in a table as One to Many relationship.

The Examination entity owns the relationship, so you should map the examinations in ClassYear entity (which is the non-owning side of the relationship) using mappedBy like this (without join column):
#OneToMany(mappedBy="classYear", cascade={CascadeType.ALL})
public Set<Examination> getExamination() {
return examination;
}
I removed the EAGER fetch type in this example (the default for #OneToMany is LAZY). I don't know your use case but always eagerly fetching mapped collections like this is not the best idea.
If you need to reference ClassYear in other entities like HomeWork, do it the same way. The HomeWork entity will have #ManyToOne mapping to ClassYear and will be the owning side of the relationship (just like in Examination) and all the homeworks for the ClassYear can be mapped using mappedBy in the ClassYear entity.
Also one small detail:
#ManyToOne(cascade={CascadeType.ALL},fetch=FetchType.EAGER)
#ManyToOne has the EAGER fetch type as default, so you don't have to specify it manually.

Related

JPA Specification multiple join based on foreignkey

I have following relationships between three objects
public class ProductEntity {
#Id
private int id;
#OneToMany(mappedBy = "productEntity",
fetch = FetchType.LAZY)
private List<ProductInfoEntity> productInfoEntityList = new ArrayList<>();
#Column(name = "snippet")
private String snippet;
}
public class ProductInfoEntity {
#Id
private int id;
#ManyToOne
#JoinColumn(name = "product_id")
private ProductEntity productEntity;
#ManyToOne
#JoinColumn(name = "support_language_id")
private SupportLanguageEntity supportLanguageEntity;
}
public class SupportLanguageEntity {
#Id
private int id;
#Column("name")
private String name;
}
And this is actual database design
Then, I'd like to make a specification to query as followings:
select * from product_info
where product_id = 1
and support_language_id = 2;
I am also using annotation for the specification which means that I use ProductEntity_, ProductInfoEntity_ and so on.
Can you please give me a full working code for the specification for query mentioned above?
Thank you guys
To use Specification your ProductInfoEntityRepository have to extend JpaSpecificationExecutor
#Repository
public interface ProductInfoEntityRepository
extends JpaRepository<ProductInfoEntity, Integer>, JpaSpecificationExecutor<ProductInfoEntity> {
}
As far as I understand you use JPA metamodel. So then
#Autowired
ProductInfoEntityRepository repository;
public List<ProductInfoEntity> findProductInfoEntities(int productId, int languageId) {
return repository.findAll((root, query, builder) -> {
Predicate productPredicate = builder.equal(
root.get(ProductInfoEntity_.productEntity).get(ProductEntity_.id), // or root.get("productEntity").get("id")
productId);
Predicate languagePredicate = builder.equal(
root.get(ProductInfoEntity_.supportLanguageEntity).get(SupportLanguageEntity_.id), // or root.get("supportLanguageEntity").get("id")
languageId);
return builder.and(productPredicate, languagePredicate);
});
}
If you want to make specifications reusable you should create utility class contains two static methods productIdEquals(int) and languageIdEquals(int).
To combine them use Specifications(Spring Data JPA 1.*) or Specification(since Spring Data JPA 2.0)
select * from product_info where product_id = 1 and support_language_id = 2;
Should work as written. But the only thing useful will be comment.
Perhaps you want the rest of the info in all three tables?
SELECT pi.comment, -- list the columns you need
p.snippet,
sl.name
FROM product AS p -- table, plus convenient "alias"
JOIN product_info AS pi -- another table
ON p.id = pi.product_info -- explain how the tables are related
JOIN support_language AS sl -- and another
ON pi.support_language_id = sl.id -- how related
WHERE p.snippet = 'abc' -- it is more likely that you will start here
-- The query will figure out the rest.
From there, see if you can work out the obfuscation provided by JPA.

Hibernate: Storing an fixed length array in one database table row

I have been trying to find a solution to store a fixed length array as a property of an object using hibernate in the same DB table as the object not using a BLOB for the array.
I currently have a class ProductionQCSession which looks like
#Entity
public class ProductionQCSession extends IdEntity {
private Long id;
private Float velocity;
private Float velocityTarget;
private Float[] velocityProfile;
public ProductionQCSession() {
}
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Override
public Long getId() {
return id;
}
#SuppressWarnings("unused")
public void setId(Long id) {
this.id = id;
}
#Basic
public Float getVelocity() {
return velocity;
}
public void setVelocity(Float velocity) {
this.velocity = velocity;
}
#Basic
public Float[] getVelocityProfile() {
return velocityProfile;
}
public void setVelocityProfile(Float[] velocityProfile) {
this.velocityProfile = velocityProfile;
}
}
Ideally I would like the DB structure to be
id|velocity|VPValue0|VPValue1|VPValue2|VPValue3|...
21| 2.1| 0.1| 0.2| -0.1| 0.3|...
I know with a high certainty that we always have 15 items in the velocityProfile array and those values as just as much properties of the object as any other property therefore I think it makes sense to add them to the database table schema, if it's possible. I would prefer to have it this way as it would be easy to get a overview of the data just doing a raw table print.
The current code just stores the array data as a BLOB.
I have looked http://ndpsoftware.com/HibernateMappingCheatSheet.html mapping cheat sheet, but could not seem to find any good solution.
I'm I just trying to do something nobody else would do?
Essentially, you're trying to have a multi-value field, which is not a relational database concept. A normalized solution would put those into a child table, which Hibernate would let you access directly from the parent row (and return it as a collection).
If you are adamant that it should be in a single table, then you'll need to create 15 individual columns....and hope that in the future you don't suddenly need 16.
The solution ended up being using the standardised method of using a child table even though it makes the data analysis slightly more complicated. The following code was used.
#ElementCollection
#CollectionTable(name ="QCVelocityProfile")
public List<Float> getVelocityProfile() {
return velocityProfile;
}
public void setVelocityProfile(List<Float> velocityProfile) {
this.velocityProfile = velocityProfile;
}

Hibernate deletion issue with a bidirectional association

I am using Spring Data JPA (1.7.2-RELEASE) in combination with Hibernate (4.3.8.Final) and MySQL (5.5). I want to manage two entities in a bidirectional assosciation. The save and update of the enties works fine, but the deletion doesn't work.
#Entity
public class Beacon extends AbstractEntity {
#OneToMany(fetch = FetchType.EAGER, mappedBy = "beacon", cascade = ALL)
private Set<Comment> comments;
/**
* #return the comments
*/
public Set<Comment> getComments() {
return comments;
}
/**
* #param comments the comments to set
*/
public void setComments(Set<Comment> comments) {
this.comments = comments;
}
}
and
#Entity
public class Comment extends AbstractEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "beacon_id")
private Beacon beacon;
public Beacon getBeacon() {
return beacon;
}
public void setBeacon(Beacon beacon) {
this.beacon = beacon;
}
}
Having a beacon with comments stored in the database, I want to delete the comment but it doesn't work. I don't get an exception but the entity is still present in the database.
This is my unit test:
#Test
public void deleteWithStrategyCheck() {
Beacon beacon = this.beaconRepository.save(createBeacon());
Comment comment = this.commentRepository.save(createEntity());
comment.setBeacon(beacon);
comment = this.commentRepository.save(comment);
this.commentRepository.delete(comment.getId());
assertThat(this.commentRepository.exists(comment.getId())).isFalse();
assertThat(this.beaconRepository.exists(beacon.getId())).isTrue();
assertThat(this.beaconRepository.findOne(beacon.getId()).getComments()).doesNotContain(comment);
}
If I delete the comment via a sql statement it works.
You need to add orphanRemoval = true to your #OneToMany mappings, and remove the Comment from the parrent beacon.
If you delete the Comment without removing it from the parrent collection you should actually get the exception (unless you are not using InnoDB storage engine, (and ou should)).
beacon.getComments().remove(comment),
will do the work then. (with orphanRemoval you don't need to call EM.remove(comment). Without it, you need to remove the comment from the collection and call EM.remove(comment).

Using Hibernate #SQLDelete for Soft Delete across All Entities

We have a fairly complex data model and are using Hibernate and Spring Data JPA on top of MySQL. We have a base class that all domain objects extend to minimize boiler plate code. I would like to be able to add soft delete functionality across all of our domain objects using only this class. However, #SQLDelete requires the table name in the clause:
#SQLDelete(sql="UPDATE (table_name) SET deleted = '1' WHERE id = ?")
#Where(clause="deleted <> '1'")
Does anybody know of a way to generalize the SQLDelete statement and allow the extending domain objects to populate their own table names?
If you use hibernate and #SQLDelete, there's no easy solution to your question. But you can consider another approach to soft delete with Spring Data's expression language:
#Override
#Query("select e from #{#entityName} e where e.deleteFlag=false")
public List<T> findAll();
//recycle bin
#Query("select e from #{#entityName} e where e.deleteFlag=true")
public List<T> recycleBin();
#Query("update #{#entityName} e set e.deleteFlag=true where e.id=?1")
#Modifying
public void softDelete(String id);
//#{#entityName} will be substituted by concrete entity name automatically.
Rewrite base repository like this. All sub repository interfaces will have soft delete ability.
Another approach, which could be more flexible.
On Entity level create
#MappedSuperclass
public class SoftDeletableEntity {
public static final String SOFT_DELETED_CLAUSE = "IS_DELETED IS FALSE";
#Column(name = "is_deleted")
private boolean isDeleted;
...
}
Update your Entity which should be soft deletable
#Entity
#Where(clause = SoftDeletableEntity.SOFT_DELETED_CLAUSE)
#Table(name = "table_name")
public class YourEntity extends SoftDeletableEntity {...}
Create a custom Interface Repository which extends the Spring's Repository. Add default methods for soft delete. It should be as a base repo for your Repositories. e.g.
#NoRepositoryBean
public interface YourBaseRepository<T, ID> extends JpaRepository<T, ID> {
default void softDelete(T entity) {
Assert.notNull(entity, "The entity must not be null!");
Assert.isInstanceOf(SoftDeletableEntity.class, entity, "The entity must be soft deletable!");
((SoftDeletableEntity)entity).setIsDeleted(true);
save(entity);
}
default void softDeleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.softDelete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", "", id), 1)));
}
}
NOTE: If your application doesn't have the hard delete then you could add
String HARD_DELETE_NOT_SUPPORTED = "Hard delete is not supported.";
#Override
default void deleteById(ID id) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void delete(T entity) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll(Iterable<? extends T> entities) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll() {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
Hope it could be useful.

Create an entity for a 'many to many' mapping table Code First

I will start by saying this may not be conceptually correct so I will post my problem also, so if someone can help with the underlying problem I won't need to do this.
Here is a simplified version of my model.
public class MenuItem
{
public int MenuItemId { get; set; }
public string Name { get; set; }
public virtual ICollection<Department> Departments { get; set; }
private ICollection<MenuSecurityItem> _menuSecurityItems;
public virtual ICollection<MenuSecurityItem> MenuSecurityItems
{
get { return _menuSecurityItems ?? (_menuSecurityItems = new HashSet<MenuSecurityItem>()); }
set { _menuSecurityItems = value; }
}
}
public class Department
{
public int DepartmentId { get; set; }
public string Name { get; set; }
public virtual ICollection<MenuItem> MenuItems { get; set; }
}
My underlying problem is that I want to select all MenuItems that belong to a Department (Department with DepartmentId = 1 for arguments sake) and also include all MenuSecurityItems.
I am unable to Include() the MenuSecurityItems as the MenuItems navigation collection is of type ICollection and doesn't support Include(). This also doesn't seem to work Department.MenuItems.AsQueryable().Include(m => m.MenuSecurityItems)
The way I "fixed" this issue was creating an entity for the many-to-many mapping table Code First creates.
public class DepartmentMenuItems
{
[Key, Column(Order = 0)]
public int Department_DepartmentId { get; set; }
[Key, Column(Order = 1)]
public int MenuItem_MenuItemId { get; set; }
}
I then was able to join through the mapping table like so. (MenuDB being my DBContext)
var query = from mItems in MenuDb.MenuItems
join depmItems in MenuDb.DepartmentMenuItems on mItems.MenuItemId equals depmItems.MenuItem_MenuItemId
join dep in MenuDb.Departments on depmItems.Department_DepartmentId equals dep.DepartmentId
where dep.DepartmentId == 1
select mItems;
This actually worked for that particular query... however it broke my navigation collections. Now EF4.1 is throwing an exception as it is trying to find an object called DepartmentMenuItems1 when trying to use the navigation collections.
If someone could either help me with the original issue or the issue I have now created with the mapping table entity it would be greatly appreciated.
Eager loading of nested collections works by using Select on the outer collection you want to include:
var department = context.Departments
.Include(d => d.MenuItems.Select(m => m.MenuSecurityItems))
.Single(d => d.DepartmentId == 1);
You can also use a dotted path with the string version of Include: Include("MenuItems.MenuSecurityItems")
Edit: To your question in comment how to apply a filter to the MenuItems collection to load:
Unfortunately you cannot filter with eager loading within an Include. The best solution in your particular case (where you only load one single department) is to abandon eager loading and leverage explicite loading instead:
// 1st roundtrip to DB: load department without navigation properties
var department = context.Departments
.Single(d => d.DepartmentId == 1);
// 2nd roundtrip to DB: load filtered MenuItems including MenuSecurityItems
context.Entry(department).Collection(d => d.MenuItems).Query()
.Include(m => m.MenuSecurityItems)
.Where(m => m.Active)
.Load();
This requires two roundtrips to the DB and two queries but it's the cleanest approach in my opinion which actually only loads the data you need.
Other workarounds are 1) either to apply the filter later in memory (but then you have to load the whole collection first from the DB before you can filter) or 2) to use a projection. This is explained here (the second and third point):
EF 4.1 code-first: How to order navigation properties when using Include and/or Select methods?
The examples in this answer are for ordering but the same applies to filtering (just replace OrderBy in the code snippets by Where).