Custom bridge table in playframework ebean - java

I am unsuccessfuly trying to create bridge table that would resolve two #ManyToMany relations. However this table have to contain additional field. For example:
Course: -course_id - pk
Student: -student_id -pk
Bridge: -(course_id, student_id) - pk
-additional_field
My student class looks like this:
#Entity
public class Student extends Model {
#Id
#OneToMany
public List<Bridge> student_id;
}
Course class is basicaly the same.
Bridge table looks like this:
#Entity
public class Bridge extends Model{
#EmbeddedId
public compound_key student_course;
public String additional_field;
#Embeddable
public class compound_key{
#ManyToOne
public Student student_id;
#ManyToOne
public Student course_id;
}
}
Thank you for help.

I have found the following solution. This is a solution without a composite key in Bridge. I added normal #Id field in Bridge class and relations to Student and Course are normal relations.
This solution contains an additional 'id' field in the 'bridge' table in the database.
Here is the code:
Student.java:
#Entity
public class Student extends Model {
#Id
public Integer id;
#OneToMany(mappedBy="student")
public List<Bridge> bridges;
public static Finder<Integer,Student> find = new Finder<Integer,Student>(
Integer.class, Student.class
);
}
Course.java:
#Entity
public class Course extends Model {
#Id
public Integer id;
#OneToMany(mappedBy="course")
public List<Bridge> bridges;
public static Finder<Integer,Course> find = new Finder<Integer,Course>(
Integer.class, Course.class
);
}
Bridge.java:
#Entity
public class Bridge extends Model {
#Id
public Integer id;
#ManyToOne public Student student;
#ManyToOne public Course course;
public String additional_field;
public static Finder<Integer,Bridge> find = new Finder<Integer,Bridge>(
Integer.class, Bridge.class
);
}
EDIT
After many attempts I have found solution with composite key in Bridge class. Classes Student and Course are the same as in previous solution.
Bridge.java changed to following:
#Entity
public class Bridge extends Model {
Bridge() {
bridgeId = new BridgeId();
}
#EmbeddedId
protected BridgeId bridgeId;
#ManyToOne
#JoinColumn(name = "student_id", insertable = false, updatable = false)
private Student student;
#ManyToOne
#JoinColumn(name="course_id", insertable = false, updatable = false)
private Course course;
public String additional_field;
public Student getStudent() {
return student;
}
public void setStudent(Student aStudent) {
student=aStudent;
bridgeId.student_id = aStudent.id;
}
public Course getCourse() {
return course;
}
public void setCourse(Course aCourse){
course=aCourse;
bridgeId.course_id = aCourse.id;
}
}
And there is additional BridgeId.java:
#Embeddable
public class BridgeId implements Serializable
{
public Integer student_id;
public Integer course_id;
public int hashCode() {
return student_id + course_id;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
BridgeId b = (BridgeId)obj;
if(b==null)
return false;
if (b.student_id == student_id && b.course_id == course_id) {
return true;
}
return false;
}
}
What is more important in this code is:
Fields of embedded id are mapped to the same columns as ManyToOne relations.
Value to 'student_id' and 'course_id' columns are inserted from embedded id and not from relations. This is because relations have attributes 'insertable' and 'updatable' set to false.
I had to add getters and setters to 'student' and 'course' fields. In setters I am updating fields of embedded key.
Above solution has several workarounds. But I wasn't ableto find easier and cleaner one.

Related

JPA find by foreign key

I have the following class:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "Municipios")
public class Municipio {
#Id
private String nombreMunicipio;
#ManyToOne
#JoinColumn(name = "nombreProvincia", nullable = false)
private Provincia nombreProvincia;
}
how would I go about getting all the Municipios given the string ID of the province? what would the controller and repository look like? I've been trying for hours and can't find the answer
Edit: decided to post my repository and controller
public interface MunicipioRepo extends JpaRepository<Municipio, Integer> {
boolean existsByNombreMunicipio(String nombreMunicipio);
Municipio findByNombreMunicipio(String nombreMunicipio);
List<Municipio> findAllByNombreProvincia(final Provincia provincia);
}
#GetMapping("/municipiosenprovincia")
public List<Municipio> getMunicipiosEnProvincia(#RequestParam String nombreProvincia){
System.out.printf(nombreProvincia);
Optional<Provincia> miProv = provinciaRepo.findById(nombreProvincia);
return municipioRepo.findAllByNombreProvincia(miProv.get());
}
Assuming that primary key of Provincia is id:
public interface MunicipioRepo extends JpaRepository<Municipio, Integer> {
...
List<Municipio> findAllByNombreProvinciaId(final String provinciaId);
}
And just pass provinciaId to this method:
// getMunicipiosEnProvincia method in controller
...
return municipioRepo.findAllByNombreProvinciaId(nombreProvincia); // here nombreProvincia is the id of Provincia

JPA/Hibernate - ManyToMany with additional columns in join table - "has no persistent id property" for IdClass

I am trying to set up a ManyToMany Relationship in JPA with additional columns in the join table.
I followed the example and testcode from here:
http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
If I run the code I get the following exception:
Caused by: org.hibernate.AnnotationException: package.ProjectAssociationId has no persistent id property
at org.hibernate.cfg.AnnotationBinder.bindIdClass(AnnotationBinder.java:2507)
at org.hibernate.cfg.AnnotationBinder.mapAsIdClass(AnnotationBinder.java:845)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:671)
at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3466)
at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3420)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1348)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1747)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:96)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:913)
... 51 more
I tried to add different annotations to the ProjectAssociationId, but did not get it to work. Do I need any kind of ID in ProjectAssociationId? I don't think so because this is not actually persisted. But about what is hibernate complaining then about exaclty?
This is the code:
#Entity
public class Employee {
#Id
private long id;
...
#OneToMany(mappedBy="employee")
private List<ProjectAssociation> projects;
...
}
#Entity
public class Project {
#Id
private long id;
...
#OneToMany(mappedBy="project")
private List<ProjectAssociation> employees;
...
// Add an employee to the project.
// Create an association object for the relationship and set its data.
public void addEmployee(Employee employee, boolean teamLead) {
ProjectAssociation association = new ProjectAssociation();
association.setEmployee(employee);
association.setProject(this);
association.setEmployeeId(employee.getId());
association.setProjectId(this.getId());
association.setIsTeamLead(teamLead);
this.employees.add(association);
// Also add the association object to the employee.
employee.getProjects().add(association);
}
}
#Entity
#Table(name="PROJ_EMP")
#IdClass(ProjectAssociationId.class)
public class ProjectAssociation {
#Id
private long employeeId;
#Id
private long projectId;
#Column(name="IS_PROJECT_LEAD")
private boolean isProjectLead;
#ManyToOne
#PrimaryKeyJoinColumn(name="EMPLOYEEID", referencedColumnName="ID")
private Employee employee;
#ManyToOne
#PrimaryKeyJoinColumn(name="PROJECTID", referencedColumnName="ID")
private Project project;
...
}
public class ProjectAssociationId implements Serializable {
private long employeeId;
private long projectId;
...
public int hashCode() {
return (int)(employeeId + projectId);
}
public boolean equals(Object object) {
if (object instanceof ProjectAssociationId) {
ProjectAssociationId otherId = (ProjectAssociationId) object;
return (otherId.employeeId == this.employeeId) && (otherId.projectId == this.projectId);
}
return false;
}
}

Ebean: How to correctly select rows where values?

I have a question concerning Ebean. Unfortunately I cannot find any documentation to help me. I would like to select all rows where a value is true and where all members of a OneToMany relationship also have a value which is true.
Here is an example:
#Entity
public class Book extends Model {
#Id
public Long id;
public Boolean isAvailable;
public static Finder<Long, Book> find = new Finder<>(Long.class, Book.class);
}
I would like to complete the findAllByIsAvailable method:
#Entity
public class Category extends Model {
#Id
public Long id;
public Boolean isAvailable;
#OneToMany(mappedBy="category", cascade=CascadeType.ALL)
public List<Book> books;
public static Finder<Long, Category> find = new Finder<>(Long.class, Category.class);
public static List<Category> findAllByIsAvailable() {
// Find all categories where isAvailable == true and books.isAvailable == true
}
}
First you need to add a #ManyToOne relationship to your Book class as :
#Entity
public class Book extends Model {
#Id
public Long id;
public Boolean isAvailable;
#ManyToOne
public Category category;
public static Finder<Long, Book> find = new Finder<>(Long.class, Book.class);
}
Then you will be able to insert in your findAllByIsAvailable() function this
return Category.find.fetch("books").where().eq("isAvailable",true).eq("books.isAvailable",true).findList();
.fetch("books") means that you make a left join of two tables.
You can read a lot about Ebean here http://www.avaje.org/doc/ebean-userguide.pdf
There also are some examples.

How do I setup annotations for JOINED inheritance with composite PK in hibernate?

I am new to hibernate and having a tough time trying to wrap my head around setting up Joined inheritance with composite Primary Key. With my current setup, I get a:
JDBCException: could not insert: LandHolidayPackage
I am essentially looking for two things:
Are the inheritance annotations in place ?
Is the composite PK setup properly ?
DB Design:
Reference
Here are my classes and the annotations involved:
#Entity
#Table(name = "HOLIDAYPACKAGE")
public final class HolidayPackage {
private Integer idPackage;
private String name;
private Set<HolidayPackageVariant> holidayPackageVariants = new HashSet<HolidayPackageVariant>(0);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "IDHOLIDAYPACKAGE", nullable = false)
public Integer getIdPackage() {
return idPackage;
}
#OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.ALL}, mappedBy = "holidayPackage")
public Set<HolidayPackageVariant> getHolidayPackageVariants() {
return holidayPackageVariants;
}
// ommitted other part of the code
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name="HOLIDAYPACKAGEVARIANT")
public abstract class HolidayPackageVariant {
private Integer idHolidayPackageVariant;
private HolidayPackage holidayPackage;
private String typeHolidayPackage;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="IDHOLIDAYPACKAGEVARIANT", nullable=false)
public Integer getIdHolidayPackageVariant() {
return idHolidayPackageVariant;
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDHOLIDAYPACKAGE", nullable=false)
public HolidayPackage getHolidayPackage() {
return holidayPackage;
}
#Column(name="TYPEHOLIDAYPACKAGE", nullable=true)
public String getTypeHolidayPackage() {
return typeHolidayPackage;
}
// ommitted setters, equals hashCode
}
#Entity
#Table(name="LANDHOLIDAYPACKAGEVARIANT")
public final class LandHolidayPackageVariant extends HolidayPackageVariant{
private static final String LAND = "LAND";
protected LandHolidayPackageVariant() {}
public LandHolidayPackageVariant(HolidayPackage holidayPackage) {
super(holidayPackage, LAND);
}
}
#Entity
#Table(name="FLIGHTHOLIDAYPACKAGEVARIANT")
public final class FlightHolidayPackageVariant extends HolidayPackageVariant{
private static final String FLIGHT = "FLIGHT";
private Destination originCity;
protected FlightHolidayPackageVariant(){}
public FlightHolidayPackageVariant(HolidayPackage holidayPackage,
Destination originCity) {
super(holidayPackage, FLIGHT);
setOriginCity(originCity);
}
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
#JoinColumn(name="IDDESTINATION", nullable=false)
public Destination getOriginCity() {
return originCity;
}
// ommited other setters etc functions
}
You annotated the properties in stead of the fields. JPA by default tries to access the fields. If you want JPA to use the fields you have to annotate the class with #AccessType(AccessType.Field).

Hibernate, single table inheritance and using field from superclass as discriminator column

I have following kinds of classes for hibernate entity hierarchy. I am trying to have two concrete sub classes Sub1Class and Sub2Class. They are separated by a discriminator column (field) that is defined in MappedSuperClass. There is a abstract entity class EntitySuperClass which is referenced by other entities. The other entities should not care if they are actually referencing Sub1Class or Sub2Class.
It this actually possible? Currently I get this error (because column definition is inherited twice in Sub1Class and in EntitySuperClass) :
Repeated column in mapping for entity: my.package.Sub1Class column: field (should be mapped with insert="false" update="false")
If I add #MappedSuperClass to EntitySuperClass, then I get assertion error from hiberante: it does not like if a class is both Entity and a mapped super class. If I remove #Entity from EntitySuperClass, the class is no longer entity and can't be referenced from other entities:
MappedSuperClass is a part of external package, so if possible it should not be changed.
My classes:
#MappedSuperclass
public class MappedSuperClass {
private static final String ID_SEQ = "dummy_id_seq";
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = ID_SEQ)
#GenericGenerator(name=ID_SEQ, strategy="sequence")
#Column(name = "id", unique = true, nullable = false, insertable = true, updatable = false)
private Integer id;
#Column(name="field", nullable=false, length=8)
private String field;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
#Entity
#Table(name = "ACTOR")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="field", discriminatorType=DiscriminatorType.STRING)
abstract public class EntitySuperClass extends MappedSuperClass {
#Column(name="description", nullable=false, length=8)
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
#Entity
#DiscriminatorValue("sub1")
public class Sub1Class extends EntitySuperClass {
}
#Entity
#DiscriminatorValue("sub2")
public class Sub2Class extends EntitySuperClass {
}
#Entity
public class ReferencingEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#Column
private Integer value;
#ManyToOne
private EntitySuperClass entitySuperClass;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public EntitySuperClass getEntitySuperClass() {
return entitySuperClass;
}
public void setEntitySuperClass(EntitySuperClass entitySuperClass) {
this.entitySuperClass = entitySuperClass;
}
}
In my project it is done this way:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "field", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue("dummy")
public class EntitySuperClass {
// here definitions go
// but don't define discriminator column here
}
#Entity
#DiscriminatorValue(value="sub1")
public class Sub1Class extends EntitySuperClass {
// here definitions go
}
And it works. I think your problem is that you needlessly define discriminator field in your superclass definition. Remove it and it will work.
In order to use a discriminator column as a normal property you should make this property read-only with insertable = false, updatable = false. Since you can't change MappedSuperClass, you need to use #AttributeOverride:
#Entity
#Table(name = "ACTOR")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="field", discriminatorType=DiscriminatorType.STRING)
#AttributeOverride(name = "field",
column = #Column(name="field", nullable=false, length=8,
insertable = false, updatable = false))
abstract public class EntitySuperClass extends MappedSuperClass {
...
}
You can map a database column only once as read-write field (a field that has insertable=true and/or updatable=true) and any number times as read-only field (insertable=false and updatable=false). Using a column as #DiscriminatorColumn counts as read-write mapping, so you can't have additional read-write mappings.
Hibernate will set value specified in #DiscriminatorColumn behind the scenes based on the concrete class instance. If you could change that field, it would allow modifying the #DiscriminatorColumn field so that your subclass and value in the field may not match.
One fundamental: You effectively should not need to retrieve your discriminator column from DB. You should already have that information within the code, of which you use in your #DiscriminatorValue tags. If you need read that from DB, reconsider carefully the way you are assigning discriminators.
If you need it in final entity object, one good practice can be to implement an Enum from discriminator value and return store it in a #Transient field:
#Entity
#Table(name="tablename")
#DiscriminatorValue(Discriminators.SubOne.getDisc())
public class SubClassOneEntity extends SuperClassEntity {
...
#Transient
private Discriminators discriminator;
// Setter and Getter
...
}
public enum Discriminators {
SubOne ("Sub1"),
SubOne ("Sub2");
private String disc;
private Discriminators(String disc) { this.disc = disc; }
public String getDisc() { return this.disc; }
}

Categories

Resources