Is it possible to check in a #OneToManyor #ManyToMany association if the many side has a given attribut value?
For example, students visiting a lecture:
#Entity
class Lecture implements Serializable {
#Id
Integer id;
#OneToMany
Set<Student> student;
}
#Entity
class Student implements Serializable {
#Id
Integer id;
Boolean isFemale;
}
Can I enforce with a "magic" annotation that only female students are allowed to visit the lecture?
Your #OneToMany annotation will execute a SELECT statement. You might be able to filter the result with vendor proprietary annotations like #Where (https://forum.hibernate.org/viewtopic.php?f=1&t=1026210&view=next).
But it seems that you want to restrict the INSERT case. You might just use a Java bean validator. So, if your Student class had the reverse #ManyToOne attribute lecture, then you could create a validator which rejects new student objects, which are linked to a lecture AND are female. (thus implementing your desired discrimination) (see bean validation: https://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/validator-customconstraints.html#section-class-level-constraints)
But you might have guessed yourself that your constraint is not a real database constraint. It's not possible with SQL, so don't expect it to be possible with JPA.
Related
I have created a Spring Boot JPA micro service with a MySQL back end. I have two simple entities in my model, users and skills. These are used to represent the skills base within my organisation. Each user may have many skills and so I have used a one to many join to model this and can successfully assign skills to users. In my database, I can see that a users_skills table has been created by JPA to achieve this.
However, I am now struggling because, for each skill that a user has, an experience level needs to be specified (e.g. basic, advanced, expert) and I am unsure how to achieve this. I'm not sure whether 'levels' should just be an enum type within the Skill entity, or perhaps it should be a separate entity in its own right? Could I configure JPA so that it generates a users_skills_levels table which would represent this three-way relationship? Any advice would be most welcome!
These are my Entity classes: -
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private String email;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Skill> skills = new HashSet<>();
getters and setters
}
#Entity
#Table(name = "skills")
public class Skill {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
getters and setters
}
That's not possible what you try to achieve.
You should create an Entity for the users_skills_levels. E.g. UserSkillLevel This entity will then have a ManyToOne relationship to User and a ManyToOne relationship to Skills plus the attribute level.
The User has a collection of UserSkillLevel and the Skill entity as well.
Please find a more in-depth example here:
https://thoughts-on-java.org/many-relationships-additional-properties/
I have a set of Java classes with the following UML diagram:
public class Invoice {
#Id
private long id;
...
}
public class InvoiceDetail {
#Id
private long id;
...
private String productName;
private int quantity;
private double price;
}
My purpose is using JPA annotations to establish the different relationships between them. There is a composition relationship between Invoice and InvoiceDetail, which is resolved using #Embedded and #Embeddable annotations for Invoice and InvoiceDetail respectively. However, a problem appears by establishing the relationships between InvoiceDetail, Class3 and Class4. In these relationships InvoiceDetail must be annotated as #Entity. However, when a class is annotated at the same time as #Entity and #Embeddable, the corresponding server will throw a runtime error during the deployment.
Basing on the information of this website, I have written the following possible solution:
#Entity
public class Invoice {
#Id
private long id;
...
#ElementCollection
#CollectionTable(name="INVOICEDETAIL", joinColumns=#JoinColumn(name="INVOICE_ID"))
private List<InvoiceDetail> invoiceDetails;
...
}
Would be this right in order to resolve my problem?
Thanks in advance.
Although without knowing what the classes really are it is hard to tell, I suppose that you have a design problem. The composition between Class1 and Class2 says that any Class2 instance only exists within the lifecycle of a corresponding Class1 instance. But on the other hand you have Class3 instances and Class4 instances which can / must have a relationship to a Class2 instance.
What I'm trying to say is that from my point of view the relationship between Class1 and Class2 should be a simple association and not a composition. Following this path Class2 would be an Entity in JPA and then you should have your problem solved.
I usually use #Embeddable for classes whose instances never exist by themselfes and #Entity for any class whose instances can exist without other instances. An address for example could be implemented either way but not on the same system. Address would be #Embeddable if I don't want to link addresses but it had to be #Entity if I want to make sure the same address isn't saved in more than one row.
[edit: added after classes 1 and 2 were renamed to Invoice and InvoiceDetails]
Having a composition between Invoice and InvoiceDetails makes perfect sense. But I still think you should avoid the need of double personality for InvoiceDetails. I can think of two solutions (both refactorings):
If you prefer having InvoiceDetails as #Embeddable you could change the associations of Class3 and Class4 to Invoice instead of InvoiceDetails. InvoiceDetails would still be traversable via the Invoice object.
If you prefer keeping the associations as is you could declare InvoiceDetails to be an entity. You could still achieve your composition with a cascading delete (see javax.persistence.CascadeType). As it seems that InvoiceDetails already has it's own table, this probably is the better option.
I checked my JPA applications and haven't found any occurence of the same class being #Entity and #Embeddable. Honestly, I doubt if this is possible at all because the official javadoc of #Embeddable says:
Specifies a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity.
As #Entity has it's own identity, you would try to declare the same object having two identities - and this can't work.
[/edit]
[edit2: adding code for solution proposal #2]
This code should work with some assumptions (see below). This is the implementation of bi-directional navigation for a 1:n-relationship.
#Entity
public class Invoice {
#Id
private long id;
#OneToMany(mappedBy="invoice", cascade = CascadeType.ALL)
private List<InvoiceDetail> details;
}
#Entity
public class InvoiceDetails {
#Id
private long id;
#ManyToOne
#JoinColumn(name="invoice_id")
private Invoice invoice;
}
Assumptions: Tables are named like the entities, the foreign key column for invoice_details table is named "invoice_id" and both tables have a primary key column named "id". Note that the mappedBy-value "invoice" refers to the entity field while the name-value "invoice_id" refers to the database table.
Be cautious when deleting an Invoice object whose InvoiceDetails still are referenced by your Class3 or Class4 instances - you have to release these references first.
For information about JPA refer to these resources:
The Java EE 7 Tutorial: Persistence
Wikibooks: Java Persistence
Javadoc of Package javax.persistence
[/edit]
Lets assume I have the following domain model:
users
----
id (PK)
partitionkey (PK)
In the above table, the partition key is primarily used for partitioning. (MySQL requires the partitionkey to be part of the primary key). If we assume the record can be uniquely identified by the id field only, is there any harm in skipping partitionkey in the mapping. For example, is the mapping below valid:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
public Long id;
#Column(name="partitionkey")
private Long partitionKey;
}
Try to define a separate #Embeddable object with the PK fields and use it as #EmbeddedId in your #Entity class like this:
#Embeddable
public class MyCompositePK {
#Column(name="id")
private Long id;
#Column(name="partitionkey")
private Long partitionKey;
}
#Entity
#Table(name = "users")
public class User implements Serializable {
#EmbeddedId
private MyCompositePK id;
...
}
Yes, it's valid. JPA provider is not aware of any constraints or other features that exist in the tables to which entities are mapped.
However, is it a good approach, especially because we're talking about partitioning here? Keep in mind that entities are associated via ids. So, for each entity that is associated with User, JPA provider will search the associated User instance by id column only, thus partitioning column will not be included in the query. This may or may not be a problem. See this answer as well for more details.
The alternative could be using provider specific extensions like #JoinFormulas in Hibernate, but they may not be easy to get right.
I would say that going with composite ids is the most straightforward solution to go.
Here is parent class Enterprise. It has employers and one of them is president of enterprise.
#Entity
class Enterprise
{
// fields
#OneToMany
public List<Employee> getEmployers()
// implementation
#OneToOne
public Employee getPresident()
// implementation
}
Here is child Employee class. It has only info about Enterprise where he works. But question is what association should I use?
#Entity
class Employee
{
// fields
// what association should I use?
public Enterprise getEnterprise()
// implementation
}
Given that you've defined the Enterprise->Employers association with #OneToMany, which means that an Employer belongs to only one Enterprise, you should be using #ManyToOne, meaning that every Employer belongs to max. 1 Enterprise, but an Enterprise can reference many Employers.
You can define the association specifics (join columns, etc) in one of the sides only, using the mapped-by attribute in the annotation:
#Entity
class Enterprise
{
#OneToMany(mapped-by="enterprise")
public List<Employee> getEmployers()
// implementation
#OneToOne
public Employee getPresident()
// implementation
}
#Entity
class Employee
{
#ManyToOne
#JoinTable ( name="Enterprise", joinColumns={ #JoinColumn(name="ENT_ID", referencedColumnName="ENT_ID") }
public Enterprise getEnterprise()
// implementation
}
In case an Employer could be president of a different Enterprise in which he is employed (seems unlikely, unless one can be president of an enterprise without being employed by it), and in case you needed to access the Enterprise of which the Employer is president from the Employer entity, you would need to add another association, ideally with #OneToOne (you would encounter problems, because #OneToOne relations require both entities to have the same #Id class). In this case I would annotate the getPresidedEnterprise() method on Employer with #ManyToOne for practical reasons.
Use #ManyToOne annotation. It is the opposite side of a one-to-many relation. It says an employee can have one enterprise, but an enterprise can have many employees.
You should have two properties on the Employee class. Employee table should have a reference to an enterprise, ad enterprise should have a reference to an employee-president.
(you could also probably subclass an Employee based on the isPresident flag column, but I don't have experience with that)
#Entity
class Enterprise
{
// fields
#OneToMany(mappedBy="enterprise")
public List<Employee> getEmployees(){}
#OneToOne
#JoinColumn(name="PRESIDENT_ID")
public Employee getPresident(){}
}
#Entity
class Employee
{
// fields
#ManyToOne
#JoinColumn(name="ENTERPRISE_ID")
public Enterprise getEnterprise(){}
#OneToOne(mappedBy="President")
public Enterprise getMyEnterprise(){}
}
I'm currently using Eclipselink, but I know now days most JPA implementations have been pretty standardized. Is there a native way to map a JPA entity to a view? I am not looking to insert/update, but the question is really how to handle the #Id annotation. Every entity in the JPA world must have an ID field, but many of the views I have created do not conform to this. Is there native support for this in the JPA or do I need to use hacks to get it to work? I've searched a lot and found very little information about doing this.
While using the #Id annotation with fields of directly supported types is not the only way to specify an entity's identity (see #IdClass with multiple #Id annotations or #EmbeddedId with #Embedded), the JPA specification requires a primary key for each entity.
That said, you don't need entities to use JPA with database views. As mapping to a view is no different from mapping to a table from an SQL perspective, you could still use native queries (createNativeQuery on EntityManager) to retrieve scalar values instead.
I've been looking into this myself, and I've found a hack that I'm not 100% certain works but that looks promising.
In my case, I have a FK column in the view that can effectively function as a PK -- any given instance of that foreign object can only occur once in the view. I defined two objects off of that one field: one is designated the ID and represents the raw value of the field, and the other is designated read-only and represents the object being referred to.
#Id
#Column(name = "foreignid", unique = true, nullable = false)
public Long getForeignId() {
...
#OneToOne
#JoinColumn(name = "foreignid", insertable=false, updatable=false)
public ForeignObject getForeignObject() {
...
Like I said, I'm not 100% sure on this one (and I'll just delete this answer if it turns out not to work), but it got my code past a particular crash point.
Dunno if it applies to your specific situation, though. And there's an excellent chance that after 11 months, you no longer care. :-) What the hell, that "Necromancer" badge doesn't just earn itself....
In my view I have a "unique" id, so I mapped it as the Entity id.
It works very well:
#Entity
#Table(name="table")
#NamedQuery(name="Table.findAll", query="SELECT n FROM Table n")
public class Table implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="column_a")
private int columnA;
JPA - 2.5.4
CREATE MATERIALIZED VIEW IF NOT EXISTS needed_article as select product_id, count(product_id) as count from product_article group by product_id;
CREATE MATERIALIZED VIEW IF NOT EXISTS available_article as select product_id, count(product_id) as count from article a inner join product_article p
on a.id = p.article_id and a.stock >= p.amount_of group by product_id;
CREATE UNIQUE INDEX productId_available_article ON available_article (product_Id);
CREATE UNIQUE INDEX productId_needed_article ON needed_article (product_Id);
Entity.java
#Entity
#Immutable // hibernate import
#Getter
#Setter
public class NeededArticle {
#Id
Integer productId;
Integer count;
}
Repository.java
#Repository
public interface AvailableProductRepository extends CrudRepository<AvailableArticle, Integer> {
#Query("select available.productId from AvailableArticle available, NeededArticle needed where available.productId = needed.productId and available.count = needed.count")
List<Integer> availableProduct();