Building a map from another table with a composite primary key - java

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.

Related

JPA With composite key non standard

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.

How to depict joins with #Query annotation in Spring JPA Repository method

I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;

PSQLException: ERROR: syntax error at or near

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.

JPA #MapsId vs #JoinColumn(updatable=false, insertable=false)

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.

JPA Mapping Error -not provided sufficient metadata in your id class

I am trying to do mapping in JPA.
#Entity
public class Auction {
#Id
private Integer auctionId;
#OneToMany(mappedBy="auctionId")
#MapKey(name="auctionParamId")
private Map<AuctionParam, AuctionParamValue> values;
}
#Entity
public class AuctionParam {
#Id
private Integer auctionParamId;
private String description;
}
#Entity
public class AuctionParamValue {
#EmbeddedId
private AuctionParamValuePK pk;
private String value;
}
#Embeddable
public class AuctionParamValuePK {
#ManyToOne
#JoinColumn(name="auctionId")
private Auction auction;
#ManyToOne
#JoinColumn(name="auctionParamId")
private AuctionParam auctionParam;
}
Showing an error:-
.Error-Details:-Exception Description:
Entity [class
com.eaportal.domain.AuctionParamValue]
uses [class
com.eaportal.domain.AuctionParamValuePK]
as embedded id class
whose access-type
has been determined as [FIELD].
But
[class
com.eaportal.domain.AuctionParamValuePK]
does not define any [FIELD]. It is
likely that you have not provided
sufficient metadata in your id class
[class
com.eaportal.domain.AuctionParamValuePK].
If you come up with a solution please let me know.
Thanks in Advance
Tushar
You cannot use an EmbeddedId with relationships. Use an IdClass.
#Entity
#IdClass(AuctionParamValuePK.class)
public class AuctionParamValue {
#Id
#ManyToOne
#JoinColumn(name="auctionId")
private Auction auction;
#Id
#ManyToOne
#JoinColumn(name="auctionParamId")
private AuctionParam auctionParam;
#Basic
private String value;
}
public class AuctionParamValuePK {
private int auction;
private int auctionParam;
}
I think there are some errors in your Auction class. This is how I think it should look
#Entity
public class Auction {
#Id
private Integer auctionId;
#OneToMany(mappedBy="auction") // not auctionId
#MapKey(name="auctionParam") // not auctionParamId
private Map<AuctionParam, AuctionParamValue> values;
}
(The annotation values have to correspond with fields (or properties), not with columns)

Categories

Resources