How to insert to subclass without add another instance to superclass? - java

I use standard JpaRepository interfaces with the classes.
I have this classes:
User:
#Entity
#Table(name = "user")
#Inheritance(strategy = InheritanceType.JOINED)
public class User extends BaseEntity {
private Long id;
//Other Attr
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
//Getters & Setters...
}
Doctor:
#Entity
#Table(name = "doctor")
#PrimaryKeyJoinColumn(name = "id", referencedColumnName="id")
public class Doctor extends User {
private Long regNumber;
//Getters & Setters
}
I was searching and it seems that jpa/hibernate have no function/procedure for inserting a Doctor with info from an existing User.
I've tried to save a Doctor with info from a User query, it made a new User entry for the User table.
In theory, one can be an user without being a doctor, later one can become doctor.

In theory, one can be an user without being a doctor, later one can become doctor.
That's true in real life, but not with Java inheritance: an object of a given type can not become an object of another type. If you want to model that situation, inheritance is the bad tool.
You should use composition instead. A User can have a Doctor role. That's a OneToOne association, that can be mapped with the same tables as the ones you used to map your inheritance, and which allows a user to become a doctor.
It's also better design, because it allows for multiple roles: a user is a doctor AND a musician, for example. Something that would also not be possible with inheritance.

Related

Spring JPA abstract/inheritance relationships

I have a User, a Shop, and an Address entity.
Both a user and a shop can have an address.
What would be the best way to set up such a relationship using Spring JPA/Hibernate?
I had thought of creating an interface and let user and shop implement it, then use the interface in the address entity class to access the their common code. I couldn't get it to work, and I kinda feel like I'm over-complicating things. It might have looked something like this:
Addressable Address
|
+---+---+
| |
Shop User
#Entity
#Table(name = "shops")
public class Shop implements Addressable {
#OneToOne
#JoinColumn(name = "address_id")
private Address address;
#Column(name = "name", nullable = false)
private String name;
#Entity
#Table(name = "users")
public class User implements Addressable {
#OneToOne
#JoinColumn(name = "address_id")
private Address address;
#Column(name = "name", nullable = false)
private String name;
public interface Addressable {
String getName();
void setName(String name);
#Entity
#Table(name = "addresses")
public class Address {
#OneToOne
private Addressable addressable;
#Override
public String toString() {
return addressable.getName() + "\n" +
streetName + " " + streetNumber + "\n" +
postalCode + " " + cityName;
}
I might be waay off course, it doesn't quite feel right. I'm sure this is problem other people have encountered as well, though I as of yet to have found a nice if any solution.
Do you need your addresses to be separate entities? In other words, is there a specific need to store addresses in a dedicated table, or do you just want to group address related properties in a common object included in shop and user just for the purpose of code reuse? If the latter applies, you could make the address embeddable and mark it as embedded in your user and shop entities. They become additional properties in the user and shop table represented by the corresponding entities.
#Entity
#Table(name = "shops")
public class Shop {
#Embedded
private Address address;
#Entity
#Table(name = "users")
public class User {
#Embedded
private Address address;
#Embeddable
public class Address {
#Column(name = "name")
private String name;
public String getName() {...}
If you need to store them as separate entries in a dedicated table, using a "real" relationship is the correct approach (one-to-one, one-to-many annotations etc...)
Using an interface as a member of a relationship in JPA instead of a real object is not possible. A member of a relationship needs to be a POJO (a real object) because the JPA engine (like Hibernate) needs to be able to instantiate such classes in the background. Since you cannot instantiate an interface (or abstract class), you cannot use interfaces in relationships directly. One rather dirty trick could be working when defining custom converters that translate between the interface and a real POJO. This I have never tried before so I am not sure this will work. I also discourage this approach since it would imply some mechanism to choose the concrete class implementation to instantiate in your converter which seems very bad code design to me ;-)
What you also could try is create an abstract class storing the name property together with getter and setters and proper column annotations for all common properties. Mark this abstract class as MappedSuperclass and let your User and Shop class subclass this abstract class. Both subclasses have the superclass' common code inherited. The mappedsuperclass and your subclass code will be merged into one entity.
#Entity
#Table(name = "shops")
public class Shop extends Addressable{
...
#Entity
#Table(name = "users")
public class User extends Addressable{
...
#MappedSuperclass
public abstract class Addressable {
#Column(name = "name")
private String name;
public String getName() {...}
It looks like you are just using Java EE JPA, not Spring JPA or Hibernate.
There are data model patterns such as "the party model" where Person and Shop are subclasses of the same parent. (Parties have one or more Addresses and are People or Organizations, Shops are Organizations)
while you can use #MappedSuperClass to have a non-entity superclass, for your specific question I would look the Java EE JPA's #AttributeOverride. This helps to let JPA know that what in the Interface will be part of the Entity.

Hibernate multi-module project: extend entity to add many-to-many relationship

I have a project that uses Hibernate and is divided into multiple modules.
I have the following modules:
device (defines entity Device)
appstore (defines entity Application)
device-appstore-integration (adds many-to-many relationship between Device and Application).
Entities look like this:
#Entity
#Table(name = "devices")
public class Device extends AbstractAuditingEntity implements Serializable
{
#NotNull
#EmbeddedId
private DeviceIdentity identity;
// ...
}
#Entity
#Table(name = "apps")
public class App extends AbstractAuditingEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// ...
}
Now I want device-appstore-integration module to add many-to-many relationship between the two entities above.
I thought about adding entity DeviceWithInstalledApps to define this many2many relationship and used the following code:
#Entity
#Table(name = "devices")
public class DeviceWithInstalledApps extends Device
{
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "devices_installed_apps",
joinColumns = {/*...*/},
inverseJoinColumns = {/*...*/}
)
private Set<App> installedApps = new HashSet<>();
// ...
}
The problem is that Hibernate requires devices table to include dtype column and treats DeviceWithInstalledApps as a separate entity that inherits from Device but I don't actually want it to be a separate entity. It's still the same entity, just with many-to-many relationship added so that I can actually access this relationship, no columns are added so there is no actual need to provide dtype column, it simply doesn't make sense in this context.
Is there any other way to define many-to-many relationship in JPA/Hibernate so that I can actually implement business logic without getting into issues mentioned above?

How to store snapshot of a spring JPA entity in a new table with new primary key column

I have couple of entities, Person and PersonSnapshot. PersonSnapshot is same as Person, except for, it has its own primary key.
I would like use Java class hierarchy so that I need not redefine property names, but I am not able to get them to work as the two tables will have different "#Id" properties.
#Entity
#Table(name = "Person")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class Person {
#Id
private int id;
#name
private String name;
}
I need to store its snapshot as in
#Entity
#Table(name = "Person_Snapshot")
class PersonSnapshot extends Person {
#Id
private int snapShotId;
}
I don't want to create an abstract MappedSuperClass for common attributes as I will end up with 3 classes. Is there any way achieve the same result with just these two classes ?
Move everything that is common between the two classes into a superclass and annotate it with #MappedSuperclass. For more details see for example this: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html

How to save multiple entities FK to single attribute in Hibernate?

I have object Car with nested object User that can not be null.
class Car {
#Id
int id;
#NotNull
User user;
}
But I would like to upgrade Vehicle to also have option of being owned by Company. Car can not be owned by both at the same time. Is there an option in Hibernate to have FK in Car that can be used as User and Company? Here is example:
class Car {
#Id
int id;
#NotNull
Owner owner; //Can be object type User or Comapny
}
I have idea to make extra object UserRole which would have option to nest User and Company, but I'm searching for better solution/optimization that won't require extra object if it is possible in Hibernate:
class Car {
#Id
int id;
#NotNull
UserRole owner; //Can be object type User or Comapny
}
class UserRole{
#Id
int id; // PK, that will be used in Car
User user;
Company company;
int type; // determinates what if owner is User or Company
}
Implementation will be used to store entites on API.
Please suggest it there is easier way. Thanks.
First of all, you're saying that User is not nullable. Then you say that Car cannot be owned by both User and Company at the same time - means User is nullable.
You can't use the same field to both Company and User. In Hibernate each class is associated with a single entity. So my suggestion is to do
class Car {
#Id
int id;
#ManyToOne
User user;
#ManyToOne
Company company;
}
without the #NotNull annotation, and then just check if null or not.
Second option is to use inheritance (if possible). you can create a #MappedSuperClass called CarHolder and then make User and Owner inherit from it. then you can hold just one field of CarHolder inside Car.
You can use both of them:
class Car {
#Id
int id;
#NotNull
User user;
#NotNull
Company company;
}
And just checks the user property and the company property for null when you need to get an owner of a car.

How to create child object of existing super object, using JOINED inheritance strategy and Hibernate

what is the appropriate way how create child object of persisted super object with Hibernate?
Consider following example:
In database is persisted User with ID 1, firstName Kevin and laseName Smith. By the time is database model extended of new Entity Auditor which is child class of User. For inheritance is used strategy JOINED, so database model now has tow tables: user and auditor. These tables are jointed using user_id FK.
I would like create of Kevin Smith object type Auditor and persist. Problem is that operations are transactional and Hibernate throws NonUniqueObjectException. Exists any way how to safely cast persisted object do child object? I tried to evict given User object, but still the same.
User entity
#Entity
#Table(name = "user")
#Inheritance(strategy = InheritanceType.JOINED)
public class User{
private Long id;
private String firstName;
private String lastName;
// getters and setters
}
Auditor entity
#Entity
#Table(name = "auditor")
public class Auditor extends User {
// some properties
}
Logic
public void createAuditorOfUser(final long userId) {
final User user = userService.getUserById(userId);
// ...
final Auditor auditor = new Auditor();
auditor.setId(user.getId());
auditor.setFirstName(user.getFirstName());
auditor.setLastName(user.getLastName());
userService.evict(user);
// will throw NonUniqueObjectException
auditorService.update(auditor);
// ...
}
I hope the problem is clear, if not I'll try improve description.
I think Hibernate intentionally restrict such behaviour. Of course you can do it with native SQL workaround (as you already did).
In Java also we can't cast an object of super class to a sub-class then fill new fields of same object. Instead, create a new instance of sub-class then copy fields.
Seems like one-to-one relation mapping fits better for the functionality you required.
I think the problem is that you want to create an user as a role, and a user is not a role, I think it fits more to say an auditor is a role(inheritance) and a User has a role (composition), so maybe you need to have the user common properties in one entity and the the role table is where you need to abstract your inheritance
Something like this
#Entity
#Table(name = "user")
public class User{
//user properties
//Here is where you will share the user
#optional = false, cascade = CascadeType.ALL, mappedBy = "user"
private Role role
// getters and setters
}
And here your role entity with will be the parent class
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Role implements Serializable {
#SequenceGenerator(name = "ROLE_SEQ", sequenceName = "ROLE_SEQ", allocationSize = 1)
#GeneratedValue(generator = "ROLE_SEQ")
#Id
private Long idRole;
#OneToOne(optional = false)
private User user;
//getter and setters
}
and your specific role
#Entity
#Table(name = "auditor")
public class Auditor extends Role {
// some properties
}
So in that way you can do something like this in java
User user = new User();
//set all the properties that you need for user or the reference for an existing user
.
.
//Set the role that you need
Auditor auditor = new Auditor();
auditor.setUser(user);
user.setRole(auditor);
userDAO.insert(user);

Categories

Resources