It seems to me that there is virtually no difference between the below two ways of mapping. Here is an example base on #MapsId javadoc:
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
What if I change Dependent's mapping to:
#Entity
public class Dependent {
#EmbeddedId DependentId id;
#ManyToOne
#JoinColumn("empid", insertable=false, updatable=false)
Employee emp;
}
What is the difference of the above two approach?
So, I tested #MapsId for my usage when in the table I have only one foregin key it was no different. But for tables where I have two foregin keys to one table like ...
UserTable, and EmailTable-> #MapsId(owner)UserTable owner, #MapsId(receiver) UserTable receiver i have problems with that. Hibernate throws exceptions. So i have to back to old #JoinColumn way of doing that. That was a one differemce that I met with that adnotations.
I am using combination of both #MapsId and #JoinColumn together to avoid getting extra field getting created in DB for associating the entities. IF I ignore #JoinColumn, an extra field is getting created in DB.
#Entity
public class BookingsModel implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private SlotDateModel slotDateModelObj;
#JsonProperty
String slotnumber;
#MapsId("memberid")
#JsonBackReference
#ManyToOne
#JoinColumn(name="memberid",referencedColumnName = "memberid")
#NotNull
MemberModel memberModel;
.
.
.
}
#Entity
public class MemberModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#JsonProperty
#Id
String memberid;
#JsonProperty
String name;
#JsonIgnore
String phoneno;
#JsonManagedReference
#OneToMany
Set<BookingsModel> bookings;
.
.
.
}
#Embeddable
public class SlotDateModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
String memberid;
String slotdate;
.
.
.
}
Tables generated with #JoinColumn
Table generated when #JoinColumn is commented Can notice that the extra field "member_model_memberid" is getting added.
Related
I'm trying to do a JPA mapping for an existing database. I can't really change the existing structure.
I managed to make it works, but Intellij is telling me that some column doesn't exist even if it works. So I don't know if there's a better way to do this or if it's Intellij that doesn't support all the use cases.
I simplified my mapping and table for the question.
The 2 tables with primary composite keys are:
Table_A
some_id_a
some_seq_a
Table B
some_id_a
some_seq_a
some_seq_b
And my mapping is:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany
#JoinColumn(name = "someIdA")
#JoinColumn(name = "someSeqA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
private long someSeqB;
}
}
So like I said it works but I have an error in Intellij saying that the #JoinColumn(name ="someIdA") #JoinColumn(name = "someSeqA") don't exist and is expecting something like #JoinColumn(name ="some_id_a") #JoinColumn(name = "some_seq_a").
Using it the way Intellij is telling me, JPA has en error that says: Table [table_b] contains physical column name [some_id_a] referred to by multiple logical column names: [some_id_a], [someIdA].
My mapping is ok despite Intellij but is there's a better alternative ?
Thanks
You can use a "derived identity" and map your classes like this:
#Data
#Entity(name="Table_A")
public class TableA {
#EmbeddedId
private Key key;
#OneToMany(mappedBy = "tableA")
private List<TableB> tableBs;
#Data
#Embeddable
public static final class Key implements Serializable {
private String someIdA;
private long someSeqA;
}
}
#Data
#Entity(name="Table_B")
public class TableB {
#EmbeddedId
private Key key;
#MapsId("tableAKey") // maps tableAKey attribute of embedded id
#JoinColumns({
#JoinColumn(name="some_id_a", referencedColumnName="some_id_a"),
#JoinColumn(name="some_seq_a", referencedColumnName="some_seq_a")
})
#ManyToOne
private TableA tableA;
#Data
#Embeddable
public static final class Key implements Serializable {
private TableA.Key tableAKey; // corresponds to PK type of TableA
private long someSeqB;
}
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
I have the following Pojo:
#Entity
#Table(name = "USER")
class User {
#Id
private long id;
private String name;
private int age;
private long lastVisited;
private long lastPlayed;
private long lastPayed;
...
}
I would like somehow if possible to map the Pojo like this:
#Entity
#Table(name = "USER")
class User {
#Id
private long id;
private String name;
private int age;
#Embedded
private UserStatistics statistics;
...
}
#Embeddable
class UserStatistics {
private long lastVisited;
private long lastPlayed;
private long lastPayed;
}
BUT, I DON'T want to move the statistics columns into a new
USER_STATISTICS table and do #OneToOne mapping.
Is there a Hibernate trick I can use here?
Thanks!
What you did is already enough, Hibernate does not require you to define fields for all columns in your table. It's rather the other way around - all non-transient fields should be reflected as columns in the corresponding table either using name defined in #Column annotation or generated using a naming convention used in hibernate configuration.
The example you presented is sufficient and will work, but I wouldn't recommend it as you can have two entities mapping single row at the same time.
I have what I thought was a straight forward relation in JPA. Looks like this. CompanyGroup:
#Entity
#Table
public class CompanyGroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#JoinColumn(name = "companies")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Company> companies;
}
Company:
#Entity
#Table
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "name")
private String name;
#JoinColumn(name = "users")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<User> users;
#Id
#GeneratedValue
private Long id;
}
User:
#Entity
#Table
public class User {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#Column(name = "email")
private String email;
#Id
#GeneratedValue
private Long id;
}
I have omitted setters, getters, etc.
This is not working. I'm trying to save a CompanyGroup(Has 2 companies, each company has 2 users, all entities are unique) to a fully empty database.
I persist this using Spring-Data, accessed in a service like this:
#Service
public class ConcreteCompanyGroupService implements CompanyGroupService {
#Autowired
private CompanyGroupRepository repository;
#Transactional
#Override
public void save(CompanyGroup group) {
repository.save(Collections.singleton(group));
}
}
When I try to call this method I receive this:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "User"
Position: 13
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2158)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:291)
Hopefully I have done something stupid that someone can find quickly. I don't know how to solve this.
EDIT:
The driver in my pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1211</version>
</dependency>
Your entity maps across to a table name that is an SQL reserved keyword (User). Sadly for you, your chosen JPA provider does not automatically quote the table name identifier, and so you get exceptions when referring to the table.
Solution is either to quote the table name yourself in the #Table annotation, or change the table name to not be a reserved keyword. Alternatively use a JPA provider that auto-quotes such reserved keywords for you (e.g DataNucleus)
Solution 1: As Pascal mentioned, you have to escape the table name with backslash like:
#Entity
#Table(name="\"User\"")
public class User {
...
}
Solution 2: Rename your table's anme with another name (Users)
#Entity
#Table(name="Users")
public class User {
...
}
Solution 3: Add a suffix to the table's name:
#Entity
#Table(name="APP_User")
public class User {
...
}
Solution 4: Change the entity name, e.g. ApplicationUser
#Entity
public class ApplicationUser {
...
}
The reason
PostgreSQL as some reserved SQL Key Words. For example: ABORT, ALL, ARRAY, CACHE, CUBE, USER, ... Those tokens are in the SQL standard or specific to PostgreSQL
Use the #Table annotation or change your class name from User to something else as User is a reserved keyword in sql.
I have the following tables in my DB:
statement:
id | created_date | message
and
statement_configuration
id | name | currency
and
statement_balances
statement_id | statement_configuration_id | balance
Where the statement_balances table has a composite primary key on statement_id and statement_configuration_id.
My Statement entity looks like this:
public class Statement implements Serializable {
#Id
private long id;
#Column
private String message
//I'm not sure of which annotations I need here
#OneToMany
private Map<Long, StatementBalance> statementBalancesByConfigId;
....
}
The StatementBalances entity looks like this:
public class Statement implements Serializable {
#Id
private long statmentId;
#Id
private long statementConfigurationId;
#Column
private long balance;
....
}
My goal is to build a Map of type Map<Long, StatementBalances> inside my Statement entity. The map will map the statement_configuration_id to a balance; allowing me to get all the StatementBalances that are linked to this Statement (keyed by statement_configuration_id).
Is it possible to build this map using JPA annotations?
Yes this is possible. An example solution:
#Entity
public class Statement implements Serializable {
#Id
private long id;
private String message;
#OneToMany(mappedBy = "statementId")
#MapKey(name = "statementConfigurationId")
private Map<Long, StatementBalances> statementBalancesByConfigId;
}
#Entity
#Table(name = "statement_configuration")
public class StatementConfiguration implements Serializable {
#Id
private long id;
#OneToMany(mappedBy = "statementConfigurationId")
private Collection<StatementBalances> statementBalances;
private String name;
private String currency;
}
The StatementBalancesId composite primary key class and StatementBalances entity class allow modeling a ternary association by creating of two bidirectional relationships between them:
public class StatementBalancesId implements Serializable {
long statementId;
long statementConfigurationId;
// requires no-arg constructor, equals, hashCode
}
#Entity
#Table(name = "statement_balances")
#IdClass(StatementBalancesId.class)
public class StatementBalances implements Serializable {
#Id
#ManyToOne
#JoinColumn(name="statement_configuration_id")
private StatementConfiguration statementConfigurationId;
#Id
#ManyToOne
#JoinColumn(name="statement_id")
private Statement statementId;
#Column
private long balance;
}
The database tables created this way are identical as those in the question.
I'm new here and I'm also new in JPA! I developed a little JPA Application with the following entities:
A Customer(Name, Prename, ID) has got an Address(ZIP_Code, city, ID). The relation between these entities is ManyToOne (Customer's perpective) and OneToMany (Address' perspective).
The code (parts) are the following: (without getters/setters)
#Entity
public class AddressEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String city;
private int zipCode;
#Column
#ElementCollection(targetClass=CustomerEntity.class)
private List<CustomerEntity> customers;
public AddressEntity() {}
#OneToMany(cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="addressentity")
public List<CustomerEntity> getCustomers() {
return customers;
}
#Entity
public class CustomerEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int primaryKey;
private String preName, surName;
#ManyToOne(targetEntity=AddressEntity.class, cascade=CascadeType.ALL)
#JoinColumn(name="Address_ID")
private AddressEntity address;
public CustomerEntity() {}
}
Now i want to add two persons:
1) Bart Simpson 1234 Springfield
2) Homer Simpson 1234 Springfield
The problem is that in the address table Springfield appears twice. But that's not the sense of normalized database! How can I realize that an AddressEntity is only added when it does not exists before!
Thanks for answering,
eniac
Persisting new addresses
It is possible to have many AddressEntities with the same City and Zipcode. If you require that a given CustomerEntity is related to the same address, then you must use that specific entity. So use the same AddressEntity that you used for Bart for Homer also
CustomerEntity bart = new CustomerEntity();
//Set Barts’s fields.
CustomerEntity homer = new CustomerEntity();
//Set Homers’s fields.
AddressEntity simpsonsPlace = new AddressEntity();
//set 1234 and Springfield.
bart.setAdress(simpsonsPlace);
homer.setAdress(simpsonsPlace);
List<CustomerEntity> simpsons = new ArrayList<CustomerEntity>();
simpsons.add(bart);
simpsons.add(homer);
simpsonsPlace.setCustomers(simpsons);
yourEntityManager.persist(bart);
yourEntityManager.persist(homer);
Associating with existing addresses.
If you are creating homer sometime after bart then you will want to associate bart to an existing address. It maybe that you will pass in the address from the client. This will have the id field populated and will be a detached entity. You can use this entity with the em.merge() operation;
CustomerEntity homer = new CustomerEntity();
homer.setAdress(detachedAddress);
yourEntityManager.merge(homer);
A better way might be to form a compound PK of Zip/HouseNumber for example. This ensures that any AddressEntity with Zip/HouseNumber combination already in the DB will be treated as a detached object, and will of course only appear once in the DB.
Embeddables
You have both #ElementCollection and #OneToMany targeted to your CustomerEntity Entity.
As per JPA Spec 11.1.14;
The ElementCollection annotation defines a collection of instances of
a basic type or embeddable class.
It is therefore incorrect to target an entity with #ElementCollection.
You require a relation between your two entities, therefore, in you your AddressEntity you just need to use the #OneToMany and drop the #ElementCollection. You also need to map (with mappedBy) to the field of the target class and not the target class itself;
#Entity
public class AddressEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String city;
private int zipCode;
#OneToMany(cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="address") //Not addressentity
private List<CustomerEntity> customers;
public AddressEntity() {}
public List<CustomerEntity> getCustomers() {
return customers;
}
Alternatively you can define your CustomerEntity as an #Embeddable and not an #entity. In this way you can use #ElementCollection to target CustomerEntity from AddressEntity, but CustomerEntity would not be an entity in its own right and cannot be processed independently as it is dependent on AddressEntity