JPA Many-To-Many relationship with an extra enum column [duplicate] - java

This question already has an answer here:
Many-to-Many Relationship (with properties) in Google App Engine for Java
(1 answer)
Closed 7 years ago.
I'm trying to persist an entity that has a Map of Objects to Enum into a Google App Engine datastore. The entity classes are annotated using JPA.
Event class
import com.google.appengine.datanucleus.annotations.Unowned;
import com.google.appengine.api.datastore.Key;
import java.util.Map;
import javax.persistence.*;
import lombok.Builder;
import lombok.Data;
#Entity
#Builder
public #Data class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
// I want a map belonging to event in order to query a particular user whether he confirmed his participation in the event
// All addressees are initially present in this map with response set to UNDEFINED
// If user has received and read notification, than the response is updated to YES, NO, or MAYBE
#Unowned
#ElementCollection
#CollectionTable(name = "user_response")
#MapKeyJoinColumn(name = "user_id")
#Enumerated
#Column(name = "response")
private Map<User, Response> addressees;
}
Response class
public enum Response {
UNDEFINED, YES, NO, MAYBE
}
I haven't defined any references in User class to this map. It's a unidirectional relationship.
User class
#Entity
public #Data class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
}
The Event.addressees column seems pretty tricky. So I ran my test to check if everything was working correctly. Well, it was not. I got an exception when I tried to save an Event entity to the datastore:
java.lang.IllegalArgumentException: addressees: Response is not a supported property type.
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:235)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:199)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:173)
at com.google.appengine.api.datastore.DataTypeUtils.checkSupportedValue(DataTypeUtils.java:148)
at com.google.appengine.api.datastore.PropertyContainer.setProperty(PropertyContainer.java:101)
According to DataNucleus Enum is a persistable data type by default. So I don't understand why I get the error message saying "Response is not a supported property type".
I suspected that the problem was with the User class. Maybe the association from Event to Users was not enough, and User should also have an association to Events. So I've added the events field to User as follows:
#Unowned
#ElementCollection
#CollectionTable(name = "user_event_responses")
#ManyToMany(mappedBy="addressees", targetEntity = Event.class)
#MapKeyJoinColumn
#Enumerated
#Column(name = "response")
private Map<Event, Response> events;
It didn't work anyway. Then I've read similar questions, and found no quick answer.
Please, show me an example of many-to-many relationship with an extra column in DataNucleus / JPA!

Problem of creating two classes that have a Many-To-Many relationship, but the relational join table has additional data, is a frequent problem.
Good examples on this topic I've found at WikiBooks - Java Persistence / Many-To-Many and in the article Mapping a Many-To-Many Join Table with extra column using JPA by Giovanni Gargiulo. References in the official documentation I've found much, much later: Unowned Entity Relationships in JDO and Unsupported Features of JPA 2.0 in AppEngine.
In this case the best solution is to create a class that models the join table.
So an EventUserResponse class would be created. It would have a Many-To-One to Event and User, and an attribute for the additional data. Event and User would have a One-To-Many to the EventUserResponse. Unfortunately I didn't managed how to map a composite primary key for this class. And DataNucleus Enhancer refused to enhance an entity class without primary key. So I've used a simple auto-generated ID.
The result should be like
Here are the sources:
EventUserAssociation class
#Entity
#Table(name = "event_user_response")
#NoArgsConstructor
#AllArgsConstructor
#Getter #Setter
#EqualsAndHashCode(callSuper = true, exclude = {"attendee", "event"})
public class EventUserAssociation extends AbstractEntity {
#Unowned
#ManyToOne
#PrimaryKeyJoinColumn(name = "eventId", referencedColumnName = "_id")
private Event event;
#Unowned
#ManyToOne
#PrimaryKeyJoinColumn(name = "attendeeId", referencedColumnName = "_id")
private User attendee;
#Enumerated
private Response response;
}
If Lombok annotations (#NoArgsConstructor for example) seem unfamiliar to you, you may want to take a look at the ProjectLombok. It does a great job to save us from a boilerplate code.
Event class
#Entity
#Builder
#EqualsAndHashCode(callSuper = false)
public #Data class Event extends AbstractEntity {
/* attributes are omitted */
// all addressees are initially present in this map with response set to UNDEFINED
// if user has received and read notification, than the response is updated to YES, NO, or MAYBE
#Singular
#Setter(AccessLevel.PRIVATE)
#OneToMany(mappedBy="event", cascade = CascadeType.ALL)
private List<EventUserAssociation> addressees = new ArrayList<>();
/**
* Add an addressee to the event.
* Create an association object for the relationship and set its data.
*
* #param addressee a user to whom this event notification is addressed
* #param response his response.
*/
public boolean addAddressee(User addressee, Response response) {
EventUserAssociation association = new EventUserAssociation(this, addressee, response);
// Add the association object to this event
return this.addressees.add(association) &&
// Also add the association object to the addressee.
addressee.getEvents().add(association);
}
public List<User> getAddressees() {
List<User> result = new ArrayList<>();
for (EventUserAssociation association : addressees)
result.add(association.getAttendee());
return result;
}
}
User class
#Entity
#NoArgsConstructor
#RequiredArgsConstructor
#Getter #Setter
public class User extends AbstractEntity {
/* non-significant attributes are omitted */
#Setter(AccessLevel.PRIVATE)
#Unowned
#OneToMany(mappedBy="attendee", cascade = CascadeType.ALL)
private List<EventUserAssociation> events = new ArrayList<>();
public static User find(String attribute, EntityManager em) {
/* implementation omitted */
}
}
AbstractEntity class
#MappedSuperclass
#NoArgsConstructor
#EqualsAndHashCode
public abstract class AbstractEntity {
#Id
#Column(name = "_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Getter
protected Key id;
}
EMFService class
public abstract class EMFService {
#Getter
private static final EntityManagerFactory emfInstance = Persistence.createEntityManagerFactory("transactions-optional");
}
Example of usage:
EntityManager em = EMFService.getFactory().createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
User fromContact = User.find(fromId, em);
Event event = Event.builder()
/* attributes initialization */
.build();
em.persist(event);
User toUser = User.find(toId, em);
event.addAddressee(toUser, Response.UNDEFINED);
tx.commit();
} finally {
if (tx.isActive()) tx.rollback();
em.close();
}
Cross-group transactions should be allowed for this to work (what if they aren't?). Add the following property to persistence.xml:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
At last, regarding the code in the question, it is not allowed to have a primary key with the name key in AppEngine.

Related

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?

Hibernate and multiple OneToOne without creating new columns

I have the following situations with multiple OneToOne reletanships:
#Table(name = "User")
public class User {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Account")
public class Account {
#OneToOne(mappedBy = "settingColumnName")
private Settings setting;
}
#Table(name = "Settings")
public class Settings{
#OneToOne()
#JoinColumn(name = "userColumnName")
private User user;
#OneToOne()
#JoinColumn(name = "accountColumnName")
private Account account;
}
Now, the issue here is that I have to create and save each model independently, because they are created as a result of StreamEvent capturing. Also, Hibernate will create automatically userColumnName and accountColumnName. What I would really need to do is to have something this:
Is this possible to implement with Hibernate? Could someone provide an example?
Do
#JoinColumn(name="userColumnName", insertable=false,updatable=false),
#JoinColumn(name="accountColumnName", insertable=false,updatable=false),
And Add two more fields in Settings Entity for these tow column and Map with same Column

JPA - Reload #OneToMany dependencies automatically

I have this scenario:
public abstract class AbstractEntity {
#Id #GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
}
public class User extends AbstractEntity {
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<Dependent> dependents;
}
public class Dependent extends AbstractEntity {
#ManyToOne
#JoinColumn
private User user;
}
When I try to insert() a new User instance with some Dependent's that already are present in database, what means they have the id field populated, I get a Detached entity passed to persist exception.
It can be solved by manually loading all the Dependents from database, but it don't look right.
I would like to know how to make JPA automatically load them when id field is populated. How can I do that?
If you are using JPA API use:
EntityManager.merge(T t)
instead of
EntityManager.persist(Object object)
Since you are using CascadeType.ALL, when you use merge, the JPA Provider will try to update the Users (in case they exists in database) or will create new ones.
Documentation: http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html.

JPA SubGraph to define fetchtype of an embedded property

I have an entity Ride which embeds an embeddable "entity" Route. Route has a List property towns with ManyToMany relation, so it has fetchtype LAZY (and I don't want to use EAGER). So I want to define an NamedEntityGraph for the entity Ride, to load load a Ride object with a Route with instantied List of towns.
But when I deploy my war, I get this exception:
java.lang.IllegalArgumentException: Attribute [route] is not of managed type
Ride
#Entity
#NamedQueries({
#NamedQuery(name = "Ride.findAll", query = "SELECT m FROM Ride m")})
#NamedEntityGraphs({
#NamedEntityGraph(
name = "rideWithInstanciatedRoute",
attributeNodes = {
#NamedAttributeNode(value = "route", subgraph = "routeWithTowns")
},
subgraphs = {
#NamedSubgraph(
name = "routeWithTowns",
attributeNodes = {
#NamedAttributeNode("towns")
}
)
}
)
})
public class Ride implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Embedded
private Route route;
// some getter and setter
}
Route
#Embeddable
public class Route implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToMany
private List<Town> towns;
// some getter and setter
}
Looking at Hibernate's implementation of org.hibernate.jpa.graph.internal.AttributeNodeImpl lead us to the conclusion that #NamedAttributeNode cannot be:
simple types (Java primitives and their wrappers, strings, enums, temporals, ...)
embeddables (annotated with #Embedded)
element collections (annotated with #ElementCollection)
if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED ) {
throw new IllegalArgumentException(
String.format("Attribute [%s] is not of managed type", getAttributeName())
);
}
I didn't find similar restriction in JPA 2.1 spec, therefore it may be Hibernate's shortcoming.
In your particular case the problem is that #NamedEntityGraph refers to the Route class which is an embeddable, thus its usage in the entity graph seems to be forbidden by Hibernate (unfortunately).
In order to make it work you would need to change your entity model a little. A few examples that come into my mind:
define Route as entity
remove Route and move its towns field into Ride entity (simplifies the entity model)
move route field from Ride into Town entity, add map of routedTowns map to Ride entity:
#Entity
public class Ride implements Serializable {
...
#ManyToMany(mappedBy = "rides")
private Map<Route, Town> routedTowns;
...
}
#Entity
public class Town implements Serializable {
...
#ManyToMany
private List<Ride> rides;
#Embeddable
private Route route;
...
}
Of course the entity graph may require changes accordingly.

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