Composite primary key vs multiple primary keys - spring

Having this entities:
User.java:
#Entity
#NoArgsConstructor
#Getter
#Setter
public class User {
#Id
private int id;
private String username;
#OneToMany(mappedBy = "owner")
#MapKey(name = "friend_id")
private Map<User, Friendship> friends = new HashMap<>();
}
Friendship:
#Entity
#Data
//#IdClass(Friendship.class)
public class Friendship implements Serializable {
#Id
private int owner_id;
#Id
private int friend_id;
private String level;
#ManyToOne
#MapsId("owner_id")
private User owner;
#ManyToOne
#MapsId("friend_id")
private User friend;
}
I though I must have #IdClass or #EmbeddedId if I want to use two or more primary keys. But as shown above, I could ommit either, and just declare two primary keys (this is what I mean it "compiles"). So the question is, why to even bother using either of those annotations and just declare more keys?
generated table:
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| owner_id | int | NO | PRI | NULL | |
| friend_id | int | NO | PRI | NULL | |
| level | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+

As it's mentioned in the hibernate documentation:
The restriction that a composite identifier has to be represented by a "primary key class" (e.g. #EmbeddedId or #IdClass) is only JPA-specific.
Hibernate does allow composite identifiers to be defined without a "primary key class" via multiple #Id attributes.
Although the mapping is much simpler than using an #EmbeddedId or an #IdClass, there’s no separation between the entity instance and the actual identifier. To query this entity, an instance of the entity itself must be supplied to the persistence context.
#Entity
public class Friendship implements Serializable {
/*
It's better to use object wrapper classes instead of the corresponding
primitive types. Because, for example, uninitialized Integer is null,
but uninitialized int is 0 that can be a legal id.
*/
#Id
private Integer ownerId;
#Id
private Integer friendId;
public Friendship() {
}
public Friendship(Integer ownerId, Integer friendId) {
this.ownerId = ownerId;
this.friendId = friendId;
}
// ...
}
Friendship friendship = entityManager.find(Friendship.class, new Friendship(ownerId, friendId));

Related

Implementing State management for a Spring boot application

I am working on a Spring boot application, where I need to implement a state transition table in the following format, where the combination of action and condition determines the transition of the entity from one state to another state:
From State| To State| User Action | Condition
----------------------------------------------------------------
S0 | S1 | Create a record |
-----------------------------------------------------------------
S1 | S1 | Update a record |
-----------------------------------------------------------------
S1 | S1 | Run validation |
-----------------------------------------------------------------
S1 | S2 | Mark state as S2 | Record is valid
In this application, there are already APIs for Create, Update, and Running Validation.
As per the requirement, the state machine should be configurable, so that any new state can be added/existing state can be deleted as well as action and condition can be changed for a given transition through the dashboard.
To accomodate this requirement, I have thought of considering State, Action, Condition and StateTransition as separate entities as follows:
Action entity:
#Entity
#Table(name = "action")
public class Action implements Serializable {
#Id
private String id;
#Column(name = "action_type")
#Enumerated(EnumType.STRING)
private ActionType actionType;
#Column(name = "action_description")
private String actionDescription;
}
Action type enum:
Enum ActionType {
CREATE_RECORD, UPDATE_RECORD, RUN_VALIDATION;
}
State entity:
#Entity
#Table(name = "state")
public class State implements Serializable {
#Id
private String id;
#Column(name = "name")
private String name;
}
Condition entity (Cannot understand how to do this)
#Entity
#Table(name = "condition")
public class Condition implements Serializable {
#Id
private String id;
#Column(name = "description")
private String description;
... CANNOT UNDERSTAND WHAT ELSE SHOULD I PUT HERE
}
State Transition entity as follows:
#Entity
#Table(name = "state_transition")
public class State implements Serializable {
#Id
private String id;
#Column(name = "from_state")
private String fromStateId;
#Column(name = "to_state")
private String toStateId;
#Column(name = "action_id")
private String actionId;
#Column(name = "condition_id")
private String conditionId;
}
Basically, with the combination of current state, action, and condition, I should be able to figure out the next state from the state transition table.
But I am stuck in the following things:
How should I implement the User Action? As I mentioned before, some of these actions as I specified above have been there in the application as APIs. Basically, I cannot understand how can I tie the user action entity that I would like to implement with the existing flow.
How should I implement the Condition as an entity ? Condition is basically an expression that either evaluates to True/False. I am not getting any clue as, how can I capture this as an entity.
Could anyone please give any pointer regarding this? Thanks.
I don't see a reason for introducing all these entities. Action seems to be static i.e. you are defining the possible actions in code, so why bother and create an entity for that? Condition is just a field in the Transition entity containing an expression. I also don't think you need to have a dedicated entity for State, but that might be debatable. Overall, I would suggest you serialize the state machine as JSON though because you will always read the entire state machine anyway. So unless you want to be able to query or refer to dedicated elements in that definition (referential integrity), don't model this as tables. So either
#Entity
#Table(name = "state_transition")
public class StateTransition implements Serializable {
#Id
private String id;
#Column(name = "from_state")
private String fromState;
#Column(name = "to_state")
private String toState;
#Column(name = "action")
private ActionType action;
#Column(name = "condition")
private String condition;
}
or
#Entity
#Table(name = "state_machine")
public class StateMachine implements Serializable {
#Id
private String id;
#Column(name = "definition")
private String definition;
}
You can then de-serialize the definition or even do that in an AttributeConverter to whatever model that suits your needs.

Unable to persist child class records using spring data jpa

Problem
1)I created a class employee that extends Person.I can persist employee records and not person.
Solution I implemented
1)Created modal classes Employee and Person.
Modal Classes
Employee
#Entity
#Table(name = "employee",
indexes = {#Index(name = "username", columnList = "username", unique = true)})
public class BaseEmployee extends Person {
public static final String OBJECT_KEY = "EMPLOYEE";
#Id
#GeneratedValue
private Long id;
// Who created this baseEmployee
private String userId;
// #Indexed(unique = true)
private String username;
#Enumerated(EnumType.STRING)
private ROLE role;
#Enumerated(EnumType.STRING)
private STATUS status;
private String designation;
Person
public class Person extends AbstractEntity {
private String firstName;
private String lastName;
private String contact;
private String email;
#Enumerated(EnumType.STRING)
private GENDER gender;
private String imageId;
private String address;
private String locationId;
private DateTime dob;
private double[] location;
private String pinCode;
Employee Controller
#PostMapping
ApiResponse<BaseEmployee> post(#RequestBody CreateEntry<BaseEmployee> baseEmployeeCreateEntry) {
BaseEmployee baseEmployeeToCreate = baseEmployeeCreateEntry.getEntry();
baseEmployeeToCreate.setStatus(STATUS.ACTIVE);
baseEmployeeToCreate = employeeService.post(baseEmployeeToCreate);
if (baseEmployeeToCreate != null)
authenticationService.setPassword(baseEmployeeToCreate, baseEmployeeCreateEntry.getPassword());
return ApiResponse.success().message("Created Successfully!").object(baseEmployeeToCreate);
}
public class CreateEntry<T> {
private T entry;
private String password;
Service
#Override
public <E extends BaseEmployee> E post(E employee) {
employee = employeeRepository.save(employee);
System.out.println(employee);
LOG.info("Admin data" + employee);
LOG.info("PUT employee {} {} {}", employee.getId(), employee.getFirstName(), employee.getEmail());
return employee;
}
Repository
public interface EmployeeRepository extends JpaRepository<BaseEmployee, Long> {}
#Expected Output
The Employee class Columns and Person class Columns
#Output Shown
mysql> select * from employee;
id | created_at | created_by | last_modified_at | last_modified_by | version | designation | role | status | user_id | username |
1 | 1519624346694 | anonymousUser | 1519624346694 | anonymousUser | 0 | csdcs | ADMIN | ACTIVE | string | admin |
3 | 1519624856504 | anonymousUser | 1519624856504 | anonymousUser | 0 | csdcs | ADMIN | ACTIVE | string | admin1 |
4 | 1519626598478 | anonymousUser | 1519626598478 | anonymousUser | 0 | csdcs | ADMIN | ACTIVE | string | admin2 |
Can anyone guide me what I am doing wrong.Thankx in advance?
Can you try adding #MappedSuperclass to Person entity?

Embeddable with #ManyToOne and AccessType.Property on Id field

I am trying to define a #Embeddable class with a #ManyToOne relationship to another entity. The #OneToMany side has a #Id field annotated with #Access(AccessType.Property). Here is an example code to illustrate:
#Embeddable
class Embeddable {
#ManyToOne
private ClassA classA;
}
#Entity
class ClassA implements Serializable {
#Id
#GeneratedValue
#Access(AccessType.Property)
private Long id;
public Long getId() { return id; }
}
#Entity
class ClassB {
#ElementCollection
private List<Embeddable> embeddables;
}
The problem is that the created table definition for the class Embeddable seems to be wrong. The column for ClassA is of type bytea and is named class_a instead of bigint and class_a_id. Also if i try to persist one dataset the related entity of ClassA is saved serialized as hex..Every other #Entity works as expected. Is this a bug or an error on my part?
Edit:
Table for Embeddable (current state):
| class_b_id | class_a |
| type: int | type: typea |
|:-----------|------------:|
| 123 | A3B4738D.. |
Table for Embeddable (needed state):
| class_b_id | class_a_id |
| type: int | type: int |
|:-----------|------------:|
| 123 | 2 |

Spring Data JPA Hibernate - Extra elements appearing in #ManyToOne relationship

I have some entity classes which have a one-to-many - many-to-one relationship. I am using Spring and Hibernate.
Each TwoWayService has exactly 2 Services in my application.
Excerpts:
#Entity
#Table(name = "two_way_services")
public class TwoWayService {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "twoWayService",
fetch = FetchType.EAGER)
private List<Service> services;
public TwoWayService() {
services = new ArrayList<>();
// Add two as default
services.addAll(Arrays.asList(new Service(), new Service()));
}
public void setService1(Service service) {
services.set(0, service);
service.setTwoWayService(this);
}
public void setService2(Service service) {
services.set(1, service);
service.setTwoWayService(this);
}
...
}
#Entity
#Table(name = "services")
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#ManyToOne(optional = false)
#JoinColumn
private TwoWayService twoWayService;
public void setTwoWayService(TwoWayService twoWayService) {
this.twoWayService = twoWayService;
}
...
}
I am using Derby on the backend. The database schema is like this:
CREATE TABLE two_way_services (
id INT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
config_name VARCHAR(255) NOT NULL,
name VARCHAR(80),
admin_ip VARCHAR(32) NOT NULL,
connection_state INT NOT NULL
);
CREATE TABLE services (
id INT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
name VARCHAR(80),
type INT NOT NULL,
ruleset VARCHAR(255) NOT NULL,
two_way_service_id INT,
FOREIGN KEY (two_way_service_id) REFERENCES two_way_services(id) ON DELETE CASCADE
);
The repository interface:
public interface TwoWayServiceRepository extends Repository<TwoWayService, Integer> {
<S extends T> S save(S entity);
...
}
In my unit tests, I find that when I call findOne on a TwoWayService, I find that I have 4 Services instead of 2. Browsing the database directly shows the data as I would expect.
TwoWayService tws1 = repo.findOne(1); // get by id
assertThat(tws1.getServices().size()).isEqualTo(2); // fails, expected:<[2]> but was:<[4]>
Examining it in the debugger I see 4 elements in the services list: the two that I expect, plus 2 extra ones which are copies of the expected. I don't see where these are coming from. Why are these extra objects appearing in the list?
I am not sure but I think, it is because you add 2 services in the constructor and 1 in each setter. This makes 4 in total. You test for the amount of services, is that what you wanted to test?

Usual field as foreign key

I have two tables. I want to make between them relationship, but the thing is that the child table connects to an attribute in a parent node, which is not a PK. How can I assign a non-PK field as a FK for a table?
Here are the tables. User Information:
#Entity
#Table(name="user")
public class userinformation implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="USER_ID")
private int uID;
#Column(name="LIB_ID")
private String libID;
//Other attributes
}
Lib Information
#Entity
#Table(name="libinfo")
public class Auth {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="AUTH_ID")
private int authID;
#Column(name="LIB_ID")
private String lib_ID;
//Other attributes
}
They both should be linked through libID (surely unique). Any idea how to implement it correctly?
Given:
class User {
#Column(name = "lib_id")
private String libID;
}
you must map the Auth entity as:
class Auth {
#JoinColumn(name = "lib_id", referencedColumnName = "lib_id")
#ManyToOne
private User user;
}
Basically, referencedColumnName is used to inform the JPA provider that it should use a column other than the primary key column of the referenced entity (which is used by default with #ManyToOne mappings).

Resources