How to implement table relation into entity object? - model-view-controller

I have two tables in a database:
Grade
+--------+--------------+-----------------+
| int id | varchar name | varchar teacher |
+--------+--------------+-----------------+
| 1 | CL-2A | E. Wright |
| 2 | CL-2B | B. Springsteen |
+--------+--------------+-----------------+
Student
+--------+------------------+-----------+
| int id | varchar name | int grade |
+--------+------------------+-----------+
| 1 | Michael Wallings | 1 |
| 2 | Rowen Tress | 1 |
| 3 | Alys Harverd | 2 |
| 4 | Jeff Bass | 1 |
| 5 | Harry Farewell | 2 |
+--------+------------------+-----------+
As you've probably seen already, Student.grade refers to Grade.id with a foreign key.
Now I've made some entity objects for both tables. But how do I implement to relation between Student and Grade?
public class Student {
private int id;
private String name;
private int grade; // Keep the grade as an int.
...
or
public class Student {
private int id;
private String name;
private Grade grade; // Hold a reference to the corresponding entity object Grade.
...
or something?
The second code snippet requires me to also get the grade entity object derived from the database.

Related

Spring boot Jpa findAll Hibernate query returns empty

I am working on a simple personal project in order to learn SpringBoot.
Project Overview: Spring Boot aplication that gets data and inserts data through RestController endpoint into a MariaDb instance on a docker.
My MariaDb table is this:
\+------------------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
\+------------------+------------+------+-----+---------+----------------+
| ACCOUNT_ID | int(11) | NO | PRI | NULL | auto_increment |
| ACCOUNT_VALUE | int(11) | YES | | 0 | |
| ACCOUNT_CURRENCY | varchar(5) | YES | | NULL | |
\+------------------+------------+------+-----+---------+----------------+
My table has data (I inserted it manualy from the terminal):
MariaDB \[dev\]\> select \* from CUSTOMER_ACCOUNT;
\+------------+---------------+------------------+
| ACCOUNT_ID | ACCOUNT_VALUE | ACCOUNT_CURRENCY |
\+------------+---------------+------------------+
| 1 | 300 | RON |
| 2 | 300 | RON |
| 3 | 300 | RON |
| 4 | 300 | RON |
| 5 | 300 | RON |
| 6 | 300 | RON |
| 7 | 300 | RON |
| 8 | 300 | RON |
| 9 | 300 | RON |
| 10 | 300 | RON |
| 11 | 300 | RON |
| 12 | 300 | RON |
| 13 | 300 | RON |
| 14 | 300 | RON |
| 15 | 300 | RON |
| 16 | 300 | RON |
| 17 | 300 | RON |
| 18 | 300 | RON |
\+------------+---------------+------------------+
My Entity class is this:
#Entity
#Table(name = "CUSTOMER_ACCOUNT", schema = "dev", catalog = "")
public class CustomerAccountEntity {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "ACCOUNT_ID")
private int accountId;
#Basic
#Column(name = "ACCOUNT_VALUE")
private Integer accountValue;
#Basic
#Column(name = "ACCOUNT_CURRENCY")
private String accountCurrency;
public int getAccountId() {
return accountId;
}
public void setAccountId(int accountId) {
this.accountId = accountId;
}
public Integer getAccountValue() {
return accountValue;
}
public void setAccountValue(Integer accountValue) {
this.accountValue = accountValue;
}
public String getAccountCurrency() {
return accountCurrency;
}
public void setAccountCurrency(String accountCurrency) {
this.accountCurrency = accountCurrency;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomerAccountEntity that = (CustomerAccountEntity) o;
return accountId == that.accountId && Objects.equals(accountValue, that.accountValue) && Objects.equals(accountCurrency, that.accountCurrency);
}
#Override
public int hashCode() {
return Objects.hash(accountId, accountValue, accountCurrency);
}
}
My repository class is this :
#Repository
public interface CustomerAccountRepository extends JpaRepository\<CustomerAccountEntity,Long\> {
#Query("Select ca.accountId, ca.accountValue, ca.accountCurrency from CustomerAccountEntity ca")
List<CustomerAccountEntity> findAllCA();
}
My settings is this:
# connect via localhost on port 3306
spring.datasource.url=jdbc:mariadb://localhost:3306/dev
spring.datasource.username=root
spring.datasource.password=mypass
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.datasource.driver=cdata.jdbc.mariadb.MariaDBDriver
When I hit the controller to retrieve all the data it returns empty in browser.
I attempted to play with the settings to change the driver and hibernate dialect. Note: I do have a succesfull conection to the DB
When I start the aplication there pops up a new table in mariadb
| Tables_in_dev |
+------------------+
| CUSTOMER_ACCOUNT |
| customer_account |
+------------------+
2 rows in set (0.000 sec)
Hibernate calls goes to the lower case ones, but this one is empty. It seems it is deleted when I stop the app.
I am still learning... but can someone explain what is happening.
spring.jpa.hibernate.ddl-auto=create-drop;
having this in your properties file will drop all existing content in your table each time you restart or run your spring-boot application.
To keep all records in your tables change to spring.jpa.hibernate.ddl-auto=update;
Your repo has Long as id type while entity has int. Try to make them both of the same type Long
To avoid data loss while using mariadb in docker, you should create a docker volume and plug it to /var/lib/mysql/data when you create your container

Persist Class as key value pair SQL Table

I have a class with multiple value types.
I want to save it as key-value pair.
Let's say we have a class
class MyClass{
String p1;
Double p2;
}
I want it in DB as
id | a | b | c
1 | - | - | -
id |fkId | propetyName | propertyValue
1 |1 | p1 | hello
2 |1 | p2 | 10

how to make the relation between many to many and one to many?

Suppose we have 3 entities named A, B, and C. And Relations between these Entities are something like this:
A -- one to many --> B
B -- many to many --> C
Example tables:
A
| id |
| -- |
| 1 |
| 2 |
B
| id | a_id |
| -- | -- |
| 1 | 1 |
| 2 | 2 |
B_C
| b_id | c_id |
| -- | -- |
| 1 | 1 |
| 2 | 2 |
C
| id |
| -- |
| 1 |
| 2 |
If we wanna make the relationship between A and C in laravel, what should we do? is it possible at all?
For these situations, you can use Has Many Through relationships and for more information, you can follow the laravel documentation.
to get the relation between your A and C model you can use has many though like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class A extends Model
{
public function c()
{
return $this->hasManyThrough(
C::class,
B::class,
'a_id', // Foreign key on the a table...
'b_id', // Foreign key on the b table...
'id', // Local key on the a table...
'id' // Local key on the b table...);
}
}
The first argument passed to the hasManyThrough method is the name of the final model we wish to access, while the second argument is the name of the intermediate model.

JPA Spring boot attach a new Child to an existing entity in a ManyToMany relationship

I have discovered a In my opinion a strange behaver from jpa. First here is the a simplified version of my code ( If I missed something let me know)
#Data
#Entity
#Table(name = "parent")
Parent {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(
name = "parent_child_mapping",
joinColumns = #JoinColumn(name = "parent_id"),
inverseJoinColumns = #JoinColumn(name = "Child_id"))
private Set<Child> childs;
#Column(name = "name", unique = true, nullable = false)
private String name;
}
#Data
#Entity
#Table(name = "child")
Child {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#ManyToMany(mappedBy = "keywords")
private Set<Parent> parents;
#Column(name = "name", unique = true, nullable = false)
private String name;
public Child() {
this.set = new HashSet<>();
}
public Child(String name, Parent p) {
this();
this.name = name;
this.parents.add(p);
}
public Child(String name, Set<Parent> parents) {
this();
this.name = name;
this.parents.addAll(parents);
}
}
interface ChildRpo extends extends JpaRepository<Child , Long> {
Optional<Child> findByName(String name);
}
interface ParentRepo extends extends JpaRepository<Parent, Long> {
Optional<Parent> findByName(String name);
}
DB:
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
Parent p1 = ParentRepo.findById(1).get() // Parent has Child 1 & 2
Parent p2 = ParentRepo.findById(2).get() // Parent has no Childs
So now I get Data from a Rest Interface to add p2 a probably new child with a name
#Autowired
ChildRpo childRepo;
#Autowired
ParentRepo parentRepo;
#PostMapping("/example/{parentName}/{childName}")
public void add(#PathVar("parentName") String pName,
#PathVar("childName") String cName) {
// Here is the Problem I think
Parent p = parentRepo.findByName(pName).get();
p.getChilds.add(new Child(cName,p);
this.parentRepo.save(p);
}
Case1: pName = pa2 & cName = child3 (works as expected)
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 3 | 2 | | 3 | child3 |
Case2: pName = pa2 & cName = child1 (not quite what I expected) but how it is
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 3 | 2 | | 3 | child1 |
Case3: pName = pa2 & cName = child1 what I expected but how it isn't is
| Parent | | parent_child_mapping | | Child |
| id | name | | child_id | parent_id | | id | name |
| 1 | pa1 | | 1 | 1 | | 1 | child1 |
| 2 | pa2 | | 2 | 1 | | 2 | child2 |
| 1 | 2 |
I have to issues with this:
I thought that the unique attribute make a column unique so two strings with the same Content can't co exists in this row 'name' but It works. I would expect In case2 an exception.
And the second one:
How can I configuration it that it works like I wants them to work?
Do I have to load childOne's id? and Attach them to the new child1 entity?
I thought because the filed is unique that spring can decide:
The field isn't in the column so I add a new child.
The field is in the column so I will not create a new child instead I attach the parent to the old child with identical name. - but this isn't the way it works by default - is there a way that it works like described?
P.S:
Here is the domain Model from the current code:
Book -- oneToMany --> Page -- oneToMany --> Version -- ManyToOne --> Author
Book -- oneToMany --> Locations
..
How can I add 'Quick' a new Version to page with id = 5 and author with Id = 4 without loading all relationships.
EDIT: The Database is created by hibernate and here are the properties:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto= update
Unique attribute in a #column works only if the database was created automatically by JPA provider. Let the provider to recreate your db, if possible. You can do this from the properties.
spring.jpa.hibernate.ddl-auto=create-drop
You have to use a method in your jpa interface which finds (or not) a child using the name. Then check a result and create a new child, if it doesn't exist yet. This should work fine:
Parent p = parentRepo.findByName(pName).get();
Child newChild;
Optional<Child> result = childRpo.findByName(cName);
if(result != null) {
newChild = result.get();
}
else {
newChild = new Child(cName);
}
p.getChilds.add(newChild);
newChild.getParents().add(p);
parentRepo.save(p);

JPA specification to select unique rows based on single distinct column

I want to select rows that have a distinct email, see the example table below:
+----+---------+-------------------+-------------+
| id | title | email | commentname |
+----+---------+-------------------+-------------+
| 3 | test | rob#hotmail.com | rob |
| 4 | i agree | rob#hotmail.com | rob |
| 5 | its ok | rob#hotmail.com | rob |
| 6 | hey | rob#hotmail.com | rob |
| 7 | nice! | simon#hotmail.com | simon |
| 8 | yeah | john#hotmail.com | john |
+----+---------+-------------------+-------------+
The desired result would be:
+----+-------+-------------------+-------------+
| id | title | email | commentname |
+----+-------+-------------------+-------------+
| 3 | test | rob#hotmail.com | rob |
| 7 | nice! | simon#hotmail.com | simon |
| 8 | yeah | john#hotmail.com | john |
+----+-------+-------------------+-------------+
Where I don't care which id column value is returned. What JPA Specification can be used for this?
Here is the specification class,
public class UserSearchSpecification implements Specification<UserSearch> {
private static final Logger LOGGER = LogManager.getLogger(UserSearchSpecification.class);
private static final long serialVersionUID = 1L;
private List<SearchCriteria> list;
/**
* Custom JPA Specification Constructor
*
*/
public UserSearchSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria) {
list.add(criteria);
}
#Override
public Predicate toPredicate(Root<UserSearch> root, CriteriaQuery<?> arg1, CriteriaBuilder builder) {
//create a new predicate list
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (SearchCriteria criteria : list) {
if (criteria.getOperation() == (SearchOperation.GREATER_THAN)) {
predicates.add(builder.greaterThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation() == (SearchOperation.LESS_THAN)) {
predicates.add(builder.lessThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation() == (SearchOperation.GREATER_THAN_EQUAL)) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
I hate JPAs Criteria API to much to produce actual code, but the basic idea is that you create the following construct:
from MyEntity e
where e.id in (
select min(id)
from MyEntity e2
where e.email = e2.email
)
This should be expressible with the Criteria API and therefore with Spring Data JPA Specifications.

Resources