I know the basic usage of #MapKeyColumn usage, eg:
#MapKeyColumn(name = "phone_type", table = "phones")
#OneToMany(mappedBy="customer")
private Map<PhoneType, Phone> customerPhones;
that gets all the customer phones categorized by PhoneType enum.
That works fine if one Customer can have only one Phone of each type.
What I need is to create a mapping that would give me map of collections of phones, eg:
#MapKeyColumn(name = "phone_type", table = "phones")
#OneToMany(mappedBy="customer")
private Map<PhoneType, Collection<Phone>> customerPhones;
When I tried above example I've got a:
org.hibernate.AnnotationException: Use of #OneToMany or
#ManyToMany targeting an unmapped class: com.myorg.myproject.Customer.customerPhones[java.util.Collection]
Is there any way of doing that?
Related
In my database, I have tables for users, roles, and classifications. A user can have exactly one role and one classification. The tables do not contain any foreign keys. Instead, I have two join tables; user_roles and user_classifications. Each row in these tables relates a user_id to a role_id and a classification_id. This is preferable because the same role/classification can be assigned to multiple unique users. This obviates the need to have multiple rows with the same role/classification but with different foreign keys pointing to the user.
The roles and classifications tables are relatively simple, an id column and name column, the name being a VARCHAR type. I have a Spring app where the UserRepository extends JpaRepository, and the User model contains javax.persistence annotations. Here it is:
#Entity
#Table(name = "users")
public class User {
#Id
private String id;
// ... other fields here
#OneToOne
#JoinTable(
name = "user_classifications",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "classification_id",
referencedColumnName = "id"
))
private Classification classification;
#OneToOne
#JoinTable(
name = "user_roles",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private Role role;
// ... the rest of the model
}
#Entity
#Table(name = "roles")
public class Role {
#Id
private String id;
#Column(name = "role_name")
private String name;
}
#Entity
#Table(name = "classifications")
public class Classification {
#Id
private String id;
#Column(name = "classification_name")
private String name;
}
Initially, the role and classification fields were String types. Jpa did not like that though as it was expecting to map to an object that mirrored the respective table. So to make Jpa happy, I created additional POJOs for Role and Classification. And that works, I can see the objects being returned from Jpa, including the id and name fields. The problem is that my front end is expecting String types. I could modify the front end to "inspect" the returned models and only keep the relevant String data (the name). I have to imagine that I am missing something here and simply setting the String value from the object is possible. I feel like I am asking the wrong questions when searching for an answer on Google. I'm hoping by explaining my situation here I'll get better results. Your help is highly appreciated.
This does not really answer your question but it would also solve your problem.
Using entities in your, I assume, REST API is definitely not suggested. You should keep your core model defined as it makes more sense according to your business case. You should then map this core model based on the entities to DTOs that organize the model to the way it suits best the needs of someone consuming your API.
Having said that, you should try to decouple your entities from the model you make available in your API. This would allow you to change your core model and keep your API model untouched. Or the other way around. Hence, I strongly suggest you creating an API model that suits your consumers needs and mapping your entities to this model.
As suggested by João Dias, you should be using DTOs and I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(User.class)
public interface UserDto {
#IdMapping
String getId();
#Mapping("role.name")
String getRoleName();
#Mapping("classification.name")
String getClassificationName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<UserDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!
I have a convenient relation set up in which an entity has a one-to-many relationship with another, and that has a many-to-one with another. So, a LISTING has many LISTING_LINE_ITEMS, and those LISTING_LINE_ITEMS have one SERVICE_PERIOD, but a SERVICE_PERIOD has many LISTING_LINE_ITEMS. I have attempted to describe this relationship using JPA's #JoinTable as follows:
LISTING
#OneToMany
#JoinTable (name = "LISTING_LINE_ITEM", joinColumns = #JoinColumn (name = "listing_id"), inverseJoinColumns = #JoinColumn (name = "service_period_id"))
Set<ServicePeriod> servicePeriods;
LISTING_LINE_ITEM
#ManyToOne (fetch = FetchType.EAGER)
#JoinColumn (name = "listing_id", nullable = false)
Listing listing;
#ManyToOne (fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn (name = "service_period_id")
ServicePeriod servicePeriod;
SERVICE_PERIOD
#ManyToOne
#JoinTable (name = "LISTING_LINE_ITEM", joinColumns = #JoinColumn (name = "service_period_id"), inverseJoinColumns = #JoinColumn (name = "listing_id"))
Listing listing;
The obvious goal is to be able to easily obtain a list of ServicePeriods for a Listing or a single Listing for a ServicePeriod. Currently the way this is set up I'm getting an exception:
org.hibernate.HibernateException: More than one row with the given identifier was found: 361951, for class: com.gonfind.entity.ServicePeriod
I believe this is because a listing has ListingLineItems that refer to the same ServicePeriod. I'm sure that there is a way to accomplish what I'm after but I don't know what it is.
You do appear to have some problems there. On the technical / JPA side:
you cannot use LISTING_LINE_ITEM both as a join table and as an entity table. There are several reasons for this, but the main reason is that you will confuse JPA: it will try to use that table in different, incompatible ways for those two purposes.
in JPA, a bidirectional relationship is owned by exactly one side; the other side uses the mappedBy attribute of its relationship annotation to reference the owning side.
But you also have data design problems. Your constraint that line items' service periods be restricted to one of those separately associated with the same listing constitutes either
a functional dependency between non-key fields, if the listing id is not part of the line item key, or otherwise
a functional dependency on a subset of a key.
In the first case, your data fail to be in third normal form; in the second case they fail to be even in second normal form. Your trouble modeling this with JPA arises in part from the low level of normalization.
Normalizing your data properly would make things a lot easier on multiple levels. To do that, you need to remove the direct association between listings and line items, and instead associate them through service periods. You then would have:
Listing <-- one to many --> ServicePeriod <-- one to many --> LineItem
Of course, that would have implications on the structure of your application, but it's likely to be a long-term development and maintenance win, and maybe even a usability win, for the application to be aligned with the natural structure of your data like that. If you wish, you could put methods on your Listing entity to allow ListingLineItems to be managed to some extent as if they belonged directly to Listings, and vise versa.
That data organization would look something like this:
LISTING
#OneToMany(mappedBy = "listing",
fetch = FetchType.EAGER,
cascade = CascadeType.PERSIST)
Set<ServicePeriod> servicePeriods;
SERVICE_PERIOD
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "listing_id")
Listing listing;
#OneToMany(mappedBy = "servicePeriod",
fetch = FetchType.EAGER,
cascade = CascadeType.PERSIST)
Set<ListingLineItem> lineItems;
LISTING_LINE_ITEM
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "service_period_id")
ServicePeriod servicePeriod;
If you cannot restructure your data more or less that way, then you're stuck jerry-rigging something that cannot fully be described to JPA. I'm imagining a separate join table for Listing <-> ServicePeriod, a non-JPA FK constraint to that table from the entity table for line items, and, of course, proper form for the various bidirectional relationships.
Let's say that this is a class that has unique constrained field.
#Entity
public class Thing {
#Column(name = "name", unique = true)
private String name;
#ManyToOne
private Owner owner;
}
Example works just fine if new Things are created with unique names. But when different owners want to create things with the same name this approach fails.
Is it possible to set unique constraint to differ records of Things in the database based on the Owners using Hibernate/JPA functionalities (I could not find any) or should I write my own logic and dump the unique from #Column.
Perhaps it could be done with Hibernate Validator? Reading the docs I haven't found much about unique constraints.
You're looking for #UniqueConstraint
http://docs.oracle.com/javaee/5/api/javax/persistence/UniqueConstraint.html
I am trying to create a sample Report card application and want to persist a map between subject and student's grade
This is my scorecard class:
#Entity
public class ScoreCard {
#NotNull #ElementCollection #ManyToMany(cascade=CascadeType.ALL)
private Map<Subject, String> gradesForMainSubject = new HashMap<Subject, String>();
}
But when trying to save data I always end up with
Caused by: org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: gradesForMainSubject
Subject itself is a Managed entity (annotated by #Entity). Any suggestions on how can I move forward.
You cannot use both #ElementCollection and #ManyToMany at the same time for a collection field.
If the values of your collection are entities, then you can use either one of the 2: #OneToMany or #ManyToMany
If the values of your collection are non-entities, then you must use #ElementCollection.
In your case, the values of your map are String which are not entities. Therefore you need to use #ElementCollection. Remove the #ManyToMany mapping. This rule should be followed, regardless of whether you map key is an entity or not.
I have three tables with simple structure:
pub [id, name]
days [id, name]
pub_days [id, pub_id, days_id]
For some unholy reason, somebody thought that compound identity for pub_days table (that would be pub_id + days_id) is not enough and added own primary key. I can't change it now, other and larger system depends on that. #sigh
I am trying to map this to Hibernate with standard #ManyToMany JPA annotation like so (I omitted getters, setters, #Entitiy annotations and other clutter):
class Pub {
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "pub_days",
joinColumns = {#JoinColumn(name = "pub_id")},
inverseJoinColumns = {#JoinColumn(name = "days_id")})
#OrderBy("id")
private List<Day> pubOpeningDays;
}
class Day {
#Id Long id;
String name.
}
when I execute following code:
Day day = repository.find(Day.class, id);
pub.getPubOpeningDays().add(day);
repository.persist(pub);
I get this error:
ERROR: ORA-01400: cannot insert NULL into ("PUB"."pub_days"."id")
Sadly, that makes perfect sense, because I haven't mapped that ID anywhere. The thing is, I don't even want to. I want it to be generated, but not sure how do I overcome this issue with #ManyToMany mapping. Any ideas?
What you can do is like I mentioned in my comments you can create a separate entity CD which will in turn connect with two classes A and B, Now relationship would be many to many between A and B, and hence A (many to many) CD (many to many) B. Now as per your requirement whenever you need to fetch the instance of A or B, what you can do is simply fire a query in the DB with proper parameters i.e id of a or id of b this will help you get your required result.
I only see two choices, either you change your mapping to a list of PubDay as samwise-gamgee told you in the comments or you add a trigger on insert on table pub_days which set a value for the column id if it is null (it could be using a sequence). But this approach depends on the features supported by your DB.