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.
Related
How to properly delete a record from a database in one query. For example, when an entity uses the primary key of the parent entity using the #MapsId annotation, if the parent entry is deleted, it will swear that the parent's id is used in the child entity.
Code example :
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
}
#Entity
public class UserDetails {
#Id
private long id;
private String phone;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User user;
}
Here, when deleting a User using the JpaRepository delete method, an error will occur that the UserDetail uses the primary key User
First, are you sure the direction of the relation makes sense? I would have expected it to be the other way around, because the user ID and name seem to be the more basic info that you need more often.
Second, what you're doing seems like an attempt to optimize performance, because you could just as well store all the data in a single entity. Are you sure the optimization pays off? (I would guess not.) See Premature Optimization.
Third, if the relation was the other way around, you could modify the annotation to #OneToOne(cascade=CascadeType.DELETE) or #OneToOne(cascade=CascadeType.ALL) to let JPA automatically delete the other entity when the first is deleted.
For that you need to delete all foreign keys with used by primary key
or by using cascade
After that use below
In JPA we can use deleteById
or by named query
DELETE FROM EMPLOYEE WHERE ID = ?1
or my native query
delete from employee where id = ?1
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/
What is the correct way to model the following relationship shown in the image?
The identifier of the entity global_rating is a foreign key (the id of the provider entity)
Remember that I need to do it in a unidirectional way, that is, the provider entity has a global_rating but global_rating does not have a provider.
Please, I need to know what is the correct way to model this in Java if I using Hibernate as ORM. Thank you so much!
I think you are looking for something like this:
#Entity
public class Provider{
#Id
private String id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private GlobalRating globalRating;
}
#Entity
public class GlobalRating{
#Id
private String id;
}
You can read about it in the hibernate docs.
I'm working on a project in WebSphere 8.5.5 (OpenJPA 2.2.3) that needs to cascade creation and merging through a large JPA annotated entity model. We are having a very specific problem when merging grand-children either by calling EntityManager.merge() on the grand-parent or by the triggered flush at the commit of a transaction. Here are the details:
Relevant portion of entity mappings:
EntityA has a oneToMany to EntityB
EntityB has a oneToMany to EntityC
EntityC has a oneToMany to EntityD
All have bidirectional mappings. Entity A and B have single column primary keys. Entity C has a composite primary key that includes a foreign key to the primary key of Entity B. Entity D has a composite key that includes the composite key of Entity C. Please see the mappings below.
#Entity
#Table(name="TableA")
public class EntityA extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_A_ID_GEN")
#SequenceGenerator(name="TABLE_A_ID_GEN", sequenceName="TABLE_A_ID", allocationSize=1)
#Column(name="TABLE_A_ID")
private Integer id;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityA", cascade=CascadeType.ALL)
private List<EntityB> entityBList;
...
}
#Entity
#Table(name="TableB")
public class EntityB extends BaseEntity {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_B_ID_GEN")
#SequenceGenerator(name="TABLE_B_ID_GEN", sequenceName="TABLE_B_ID", allocationSize=1)
#Column(name="TABLE_B_ID")
private Integer id;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_A_ID")
private EntityA entityA;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityB", cascade=CascadeType.ALL)
private List<EntityC> entityCList;
...
}
#Entity
#Table(name="TableC")
public class EntityC extends BaseEntity {
#EmbeddedId
private EntityC_PK id = new EntityC_PK();
#MapsId("entityB_Id")
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name="TABLE_B_ID")
private EntityB entityB;
#OneToMany(fetch=FetchType.LAZY, mappedBy="entityC", cascade=CascadeType.ALL)
private List<EntityD> entityDList;
...
}
#Embeddable
public class EntityC_PK implements BaseComponent {
#Column(name="TABLE_B_ID", nullable = false, updatable = false)
private Integer entityB_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_C_ID_GEN")
#SequenceGenerator(name="TABLE_C_ID_GEN", sequenceName="TABLE_C_ID", allocationSize=1)
#Column(name="TABLE_C_ID")
private Integer entityC_Id;
...
}
#Entity
#Table(name="TABLE_D")
public class EntityD extends BaseEntity {
#EmbeddedId
private EntityD_PK id = new EntityD_PK();
#MapsId("entityC_Id")
#JoinColumns({
#JoinColumn(name = "TABLE_B_ID"),
#JoinColumn(name = "TABLE_C_ID")})
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
private EntityC entityC;
...
}
#Embeddable
public class EntityD_PK implements BaseComponent {
#Embedded
private EntityC_PK entityC_Id;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLE_D_ID_GEN")
#SequenceGenerator(name="TABLE_D_ID_GEN", sequenceName="TABLE_D_ID", allocationSize=1)
#Column(name="TABLE_D_ID")
private Integer entity_id;
...
}
What Works:
You can call an EntityManager.persist() on Entity A (with all the children attached) and the model will cascade the persist correctly.
What Doesn't Work:
If you instantiate Entity A and call EntityManager.persist(entityA) and THEN add the children, grand-children, etc. when you EntityManager.merge(entityA) (or allow the implicit merge upon committing the transaction) it will fail to execute the INSERT statements in the correct order. To make things more confusing the order of the INSERTS is not consistent across repeat executions of unit tests. It fails by attempting to insert Entity D before Entity C.
The Question:
How to we correct the JPA annotations to enforce the correct insert order (and update/delete) upon merge?
EDIT 1:
The insert/delete order is critical because the database enforces the foreign key relationships with constraints.
Let me first state (and maybe I'm stating the obvious, sorry) that you should review the JPA spec for your scenarios.......embedables sometimes have differently rules about them. Next, you state 'EntityManager.create()', but I think you meant .persist? You later talk about merge so maybe you mean .merge? Either way, I'd suggest you stick with .persist if you want to persist new entities rather than a merge. While it is not illegal, merge is typically for merging detached entities, etc.
With that out of the way, let me get at the heart of your question and give you a property which might help with your order. You didn't state in your text if your ddl contains a foreign key constraint. Since you are concerned with order, I'd assume you have such a constraint. If you do, OpenJPA knows nothing about this constraint, and as such, will not know to order things appropriately. By default, you can't depend on the order of SQL, and the randomness of the ordering is exactly what I expect. However, if you need things to be order in such a way as to support an FK constraint, then you need to allow OpenJPA to 'learn' about your constraint. To do that, you need to set this property in your persistence.xml file (or you can set it as a JVM custom property):
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
This property allows OpenJPA to inspect your schema and in so doing it can learn about your FK constraint. With that knowledge, OpenJPA can properly order SQL.
Finally, if you don't have an FK constraint, but you want to order the SQL in a certain way, then you might need to use this:
<property name="openjpa.jdbc.UpdateManager" value="operation-order"/>
Do not, and I repeat do not use both of these properties together. It can have odd side effects. Please focus on the SchemaFactory property first, and then if it doesn't help try UpdateManager. The operation-order tells OpenJPA to order SQL based on how your persist your entities, or in other words, the order of operations. This might actually not be overly helpful to your situation since you persist A and expect everything else to be cascaded (OpenJPA would likely persist A first, but when it comes to B and C, it is a crapshoot which will go first). However, if you persisted A, then C, then B, the SQL should go in order of inserting A, C, then B with "operation-order" set.
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();