Designing JPA Entity for Database Table - mysql

I am designing a voting application and designed a database with 2 tables - State and District.
The two tables are -
State details table
State_ID NUMBER(3)
State_name VARCHAR2(30)
PRIMARY KEY(State_ID)
District details table
District_ID NUMBER(3)
State_ID NUMBER(3)
District_name VARCHAR2(30)
PIN_Code NUMBER(6)
PRIMARY KEY(District_ID)
UNIQUE(State_ID, District_name)
FOREIGN KEY(State_ID)
As two states can have a district with same name, I am considering the combination of State ID and District name as UNIQUE by combination.
Now I have to design JPA Entities for these two tables, but I am unable to design because, in the database design, the District table has State ID in it as Foreign Key, but when it comes to designing entities having a State object inside District sounds meaningless, because if I keep HAS-A relationship in my mind, then District doesn't have a State in it. A State HAS-A list of Districts. This is not in accord with the above database design.
Can someone please help me in designing JPA Entities for this. Please suggest if the database design needs modification too.
Thanks.

An example JPA approach based on Rick's proposal:
#Entity
#Table(name = "States")
public class State {
#Id
#Column(nullable = false, columnDefinition = "CHAR(2) CHARACTER SET ascii")
private String code;
#Column(nullable = false, length = 30)
private String name;
#OneToMany(mappedBy = "state")
private Collection<District> districts;
public State() { }
// getters, setters, etc.
}
#Entity
#Table(name = "Districts", uniqueConstraints = {
#UniqueConstraint(columnNames = { "state_code", "name" })
})
public class District {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, columnDefinition = "SMALLINT UNSIGNED")
private short id;
#Column(name = "state_code", nullable = false,
columnDefinition = "CHAR(2) CHARACTER SET ascii")
private String code;
#Column(length = 30)
private String name;
#ManyToOne
#JoinColumn(name = "state_id")
private State state;
public District() { }
// getters, setters, etc.
}
NOTE: due to usage of columnDefinition attribute the above entities are not portable across databases.

CREATE TABLE States (
code CHAR(2) NOT NULL CHARACTER SET ascii,
name VARCHAR(30) NOT NULL,
PRIMARY KEY(code)
);
CREATE TABLE Districts
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
state_code CHAR(2) NOT NULL CHARACTER SET ascii,
...
PRIMARY KEY(id),
UNIQUE (state_code, name)
);
Notes:
* May as well use the standard state codes (AK, AL, ...) if you are talking about the US.
* Probably the only purpose of States is to spell out the name.
* I was explicit about ascii in case your table defaults to utf8.
* MySQL uses VARCHAR, not VARHAR2.
* You could get rid of Districts.id and have simply PRIMARY KEY(state_code, name), but I am guessing you have a bunch of other tables that need to join to this one, and a 2-byte id would be better than the bulky alternative.

Related

Placing indexes on a MySQL table using JPA/Hibernate

I would like to set indexes on multiple columns within a single table in MySQL database. After reading this article, I'm not 100% sure which approach to use.
So my (simplified) table looks like this:
#Data
#Entity
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "loan")
public class Loan {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "loan_id", unique = true, nullable = false)
private long id;
#Column(name = "amount", unique = false, nullable = false)
private double amount;
#Column(name = "rate", unique = false, nullable = false)
private double rate;
#Column(name = "payments", unique = false, nullable = false)
private int payments;
#Column(name = "pmt", unique = false, nullable = false)
private double pmt;
}
I will have a lot of search queries, for instance:
SELECT *
FROM Loan loan
WHERE loan.amount =: amount
AND loan.rate =: rate
AND loan.payments =: payments
AND loan.pmt =: pmt
LIMIT 1;
Now, I would like to index fields in WHERE clause. Essentially, I would like to achieve effect of a "composite key" where in table loan there are only unique combinations of mentioned fields. So I cannot have two rows all with some values.
Is there such a configuration?
ou can add a UNIQUE constraint, which would be indexed automatocally
#Table(uniqueConstraints =
{
#UniqueConstraint(name = "UniqueWhereclause", columnNames = { "amount", "rate","payments","pmt" })})
or you can create an index alone
#Entity
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "loan", indexes = {
#Index(columnList = "amount, rate,payments,pmt", name = "name_idx") })
DOUBLE is likely to cause trouble for you. Switch to DECIMAL with a suitable number of decimal places -- so that = can be tested correctly.
Also, that's a strange combination of 4 things to use in the WHERE. Any 3 of those columns should (mathematically) determine the value for the 4th.
Does the table also have the name of the person taking out the loan? Or is this table a list of possible loans? As rates change, so you add more rows to the table? But why have the table if 3 columns can be used to compute the 4th?

how can I create a Single Entity from two other tables/Entity JPA hibernate making a INNER JOIN result with a common key

How can I create a Single Entity from two other tables/Entity in JPA/hibernate making a INNER JOIN result with a common key. I was using the below code but it gives me a full join instead of an inner join. it give me records from the meal table even if the
"id 1" does not exist in the allergies table, example:
{id=1, name='tacos', description='Mexican food', price ='10',peanuts=null, celery=null, sesameSeeds=null}
How can constrain to don't return any records if the 'id' is missing from the secondary table allergies? to show only records when the primary key is present in both tables.
I want something like this instead:
{id=1, name='tacos', description='Mexican food', price ='10',peanuts='no', celery='no', sesameSeeds='no'}
Please advise.
#Entity
#Table(name = "meal")
#SecondaryTable(name = "allergens", pkJoinColumns = #PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Long id;
#Column(name = "name")
String name;
#Column(name = "description")
String description;
#Column(name = "price")
BigDecimal price;
#Column(name = "peanuts", table = "allergens")
boolean peanuts;
#Column(name = "celery", table = "allergens")
boolean celery;
#Column(name = "sesame_seeds", table = "allergens")
boolean sesameSeeds;
// standard getters and setters
}

Alternative for #Formula?

I have two objects:
#Table(name = "user")
User
#Id
Integer id
Integer uuid;
and reservation:
#Table(name = "reservation")
Reservation
#Id
Integer id;
Integer uuid;
My goal is:
#Table(name = "reservation")
Reservation
#Id
Integer id;
Integer uuid;
#Formula("(SELECT * FROM user b WHERE b.uuid = uuid )")
List<User> users;
The problem is #Formula doesnt work with objects.
How to include list of all users in reservation with same uuid?
I found this but maybe there is better option
https://stackoverflow.com/a/37502703/3871754
Support of relationships that references non-PK columns is an optional feature. In simple cases it's supported by Hibernate
#NotAudited
#OneToMany
#JoinColumn(name = "uuid", referencedColumnName = "uuid")
private List<Barrier> barriers = new ArrayList<>();
and implemented Serializable for Reservation

How to perform a cascaded merge against child of new entity with JPA and Hibernate

This question relates to managing id numbers to ensure that I don't end up with duplicate identities on my person's table. It is a springboot project with MySQL database.
Some background:
I have an HTML form for submitting an "episode". Each episode contains "persons" and has a relationship to "persons" of ManyToMany.
"episodes" are entered and submitted into the database (db1) by field staff. A few hours later the episode is manually entered into a second database (db2) by BackOffice staff.
On my spring attached database (db1) I have a persons table which has a native auto generated id field. db1 also has a id2 field - which records the unique id for the person from db2.
Field staff do not always have access to id2 when they enter a episode, but BackOffice staff do.
When I save a new "episode" I need the save method to check if person id2 exists in the database and perform an update on person (not create new).
Then delete the duplicate person.
Episode entity:
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#Column(name="start_date")
#DateTimeFormat (pattern="dd/MM/yyyy HH:mm")
private Date start_date;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "episode_person", joinColumns = #JoinColumn(name = "episode_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"))
private List<Person> persons;
#OneToOne(cascade=CascadeType.ALL)
//#JoinColumn(name = "id")
private Address address;
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long id2;
private String surname;
private String firstname;
private String phoneHome;
#DateTimeFormat(pattern = "dd/mm/yyyy")
private Date dob;
#ManyToMany(mappedBy = "persons", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Episode> episodes;
EpisodeServiceImpl
#Override
#Transactional
public Episode saveEpisode(Episode episode) {
List mergedPeople = personService.mergeDetachedWithAttached( episode.getPersons() );
episode.setPersons( mergedPeople );
return episodeRepository.save(episode);
}
PersonServiceImpl
#Transactional
#Override
public List mergeDetachedWithAttached(List<Person> people) {
final List<Person> results = new ArrayList<>();
if ( people != null && !people.isEmpty() ) {
// loop over every person
for (Person person : people) {
// for current person try to retrieve from db via Id2
Person db2Person = personRepository.findById2( person.getId2() );
// if a person was retrieved use them instead of submitted person
if (db2Person != null ) {
System.out.println("A matching person was found in the db using id2 - this is the person that will be added");
results.add(db2Person);
} else {
results.add(person);
}
}
}
return results;
The way this is written at the moment when ever a new episode is submitted I create new person(s) even if I successfully looked them up from db1 using id2 and added them to the episode.
How can I handle this so that:
I can merge duplicate identities based on comparing id2. The joining table that holds episode_id and person_id will also need to be updated where a id is deleted after a merge.
It's much easier if you replace the #ManyToMany association with 2 bidirectional #OneToMany associations, meaning that you map the association table as well.
This way, considering that you have those duplicated Episode or Person entities, you can easily move the joined association by simply adjusting the #ManyToOne associations on the association entity. As for the duplicated Episode or Person you can either use UPSERT as explained below, or do a manual merging after the entries are added to the DB by the batch process.
Typically, when you have multiple nodes that running concurrently, you can use the database UPSERT or MERGE operation.
You could combine the UPSERT with Hibernate #SqlInsert annotation.
To handle the FK updates, you'd need to use the FK ON DELETE CASCADE strategy.

JPA Primary Key in separate sequence tables

I have multiple entities annotated with the following:
#TableGenerator(name = "XXX_Gen", table = "XXX_GEN", pkColumnName = "GEN_NAME", valueColumnName = "GEN_VAL")
#GeneratedValue(strategy = GenerationType.TABLE)
#Id
private String id;
I am using MySQL behind the scenes with eclipselink and the problem is that regardless of the values that I enter in the name and table MySQL always just uses a single table called 'SEQUENCE' to increment the PK's values.
Normally this isn't an issue except I have a specific case where I need an entity to have its own incremental sequence.
I wasn't specifying the generator! It should be;
#TableGenerator(name = "XXX_Gen", table = "XXX_GEN", pkColumnName = "GEN_NAME", valueColumnName = "GEN_VAL")
#GeneratedValue(strategy = GenerationType.TABLE, generator="XXX_Gen")
#Id
private String id;
I think I need another cup of coffee!