I have an enttity in my domain:
public final class FieldDefinition {
private final FieldDefinitionId id;
private final FieldName name;
//other fields
public FieldDefinition(final FieldDefinitionId id,
final FieldName name) {
checkState(id, name, label, type);
this.id = id;
this.type = type;
}
private void checkState(final FieldDefinitionId id,
final FieldName name) {
Preconditions.checkState(nonNull(id), "id cannot be null");
Preconditions.checkState(nonNull(name), "name cannot be null");
As you can see FieldDefinitionId is required while object creation. This id is generated in database entity:
#Entity
public final class FieldDefinitionJpaEntity implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
private long id;
#Column(length = 50, nullable = false)
private String name;
....
}
There 'id' is the same as FieldDefinitionId . It is mapped like that:
private FieldDefinition asDomainField(final FieldDefinitionJpaEntity entity) {
return new FieldDefinition(
new FieldDefinitionId(entity.getId()),
new FieldName(entity.getName()),
// other fields
}
Now i am receiving FieldDefinitionDto from another service which needs to be saved to database. Here is the class
class FieldDefinitionDto {
private final String name;
//other fields
}
As you can see there is no id inside DTO object. Now when i am trying to map DTO to domain model i cannot do this because FieldDefinitionId is required. What should i do? Should i create seperate model for creation like FieldDefinitionCreation which is basically the same as FieldDefinition but without FieldDefinitionId ?. I would like to stick to id generation on database level (not on domain level with UUID). Anyone had similar problem? Is keeping 2 seperate models which are almost identical is a good idea?
Your ID generation strategy simply isn't aligned with your domain model design.
#GeneratedValue(strategy = IDENTITY) means the identity will be generated by the DB when the entity gets saved. If you choose this strategy then you can't make id a required constructor argument in the FieldDefinition's class.
I'm also a bit puzzled why you wouldn't map FieldDefinition directly with JPA rather than having FieldDefinitionJpaEntity?
Anyway, either you go with an assigned ID upfront (can still be generated in the DB) e.g.
FieldDefinitionId id = fieldDefinitionRepository.nextId();
FieldDefinition fd = new FieldDefinition(id, name);
or you go with a generated ID upon persisting e.g.
FieldDefinition fd = new FieldDefinition(name); //no ID yet
fieldDefinitionRepository.save(fd); //ID assigned
I always prefer to generate IDs upfront because it's much easier to generate domain events, among other things.
Related
How should I return the Primary Key of my table-read API requests without requiring a Primary Key on my table-write API requests? They use the same object.
I have a Person class and two controller functions.
public class Person {
private Integer id; // this is the primary key in my table
private String name;
private Integer houseId;
private Integer age;
}
#RestController
public class TestController {
#RequestMapping("/")
public List<Person> fetchAll() { // this function does a "SELECT *" on all rows
return personDao.findAll();
}
#PostMapping(value = "/createPerson")
public void createPerson(#RequestBody Person person) { // this function does an "INSERT"
personDao.insertPerson(person);
}
}
I don't want to require the table Primary Key (class attribute "id") on all my "createPerson" API requests, since the client won't know it. I want to skip on that value, and let the table increment the PK on insert.
However, I want to return the "id" in the "fetchAll" API requests. And I want it defined on the Person object.
I don't see a way to do this besides making "id" Optional type, or defining two separate objects: "PersonRequest" and "PersonResponse", both which will extend "Person" class. Both of which seems like a bad practice
Is there something I'm missing?
Since you said that you want to have the id automatically incremented make sure to add the annotations below to your id field:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
There is nothing stopping you from saving an object without an id even though it has a property id. If you do:
Person person = new Person(); //person is empty here
person = personRepository.save(person);
The return value assigned to person will have an id. Also when you execute:
Person person = personRepository.findById(id);
The person object will have the id, I am not sure what else is confusing you.
I am in middle of developing a REST API using spring data JPA and need some assistance.
My REST API will process a request to create a Group and assign members to the group based on request data. The URL for the request will be #PostMapping("/createGroup/{user-id}/groups")
I have below classes,
Member class:
public class Member {
private int memberId;
private String memberName;
private String memberCity;
// getters / setters
My Request Body class:
public class AppRequest {
private String name;
private String description;
private List<Member> members;
// getters / setters
My GroupEntity:
#Entity
#Table(name="Groups")
public class GroupEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#OneToOne
#JoinColumn(name="groupOwnerId")
private MemberEntity groupOwnerId;
private String groupName;
private String description;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(
name="Group_Members",
joinColumns= #JoinColumn(name="id"),
inverseJoinColumns= #JoinColumn(name="memberId")
)
Set<MemberEntity> members = new HashSet<>();
// getters / setters
My MemberEntity:
#Entity
#Table(name="Members")
public class MemberEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int memberId;
private String memberName;
private String memberCity;
#ManyToMany(cascade=CascadeType.ALL, mappedBy="members")
Set<GroupEntity> groups = new HashSet<>();
// getters / setters
And finally My controller,
#RestController
public class AppController {
#Autowired
MemberRepository memRepo;
#Autowired
GroupRepository groupRepo;
#PostMapping("/createGroup/{user-id}/groups")
#Transactional
public ResponseEntity<AppResponse> createGroup(#RequestBody AppRequest request,
#PathVariable(name="user-id") String userId) {
AppResponse response = new AppResponse();
GroupEntity group = new GroupEntity();
group.setGroupName(request.getName());
group.setDescription(request.getDescription());
// Code that causes error when trying to save owner ID
MemberEntity mem = new MemberEntity();
mem.setMemberId(Integer.parseInt(userId));
group.setGroupOwnerId(mem);
List<Member> members = request.getMembers();
Set<MemberEntity> storedMembers = new HashSet<>();
Set<GroupEntity> storedGroups = new HashSet<>();
storedGroups.add(group);
for(Member member : members) {
if(member.getMemberId() != 0) { // existing member
MemberEntity memberentity = new MemberEntity();
memberentity.setMemberName(member.getMemberName());
memberentity.setMemberCity(member.getMemberCity());
storedMembers.add(memberentity);
memberentity.setGroups(storedGroups);
memRepo.save(memberentity);
}
else { // new member
//some logic
}
group.setMembers(storedMembers);
groupRepo.save(group);
}
return null;
}
}
Request Body would be something like this,
{
"description": "Funny Group",
"members": [
{
"memberCity": "Mumbai",
"memberName": "Amit"
},
{
"memberId": 123
}
],
"name": "My Group"
}
What I want to achieve is when a group is created, I want to add the user-Id in the REST URL [/createGroup/{user-id}/groups] as a owner ID for that group. To achieve this I am manually creating a member entity and setting that as a groupOwnerId as shown below,
MemberEntity mem = new MemberEntity();
mem.setMemberId(Integer.parseInt(userId));
group.setGroupOwnerId(mem);
If I comment above piece of code application boots up but the groupOwnerId value is set as null which is obvious as I am not setting it anywhere. So if I write above code, application boots up properly. But when I hit the endpoint, I get below error,
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKB0L8UXRYL2TEVI7BTCMI4BYOD: PUBLIC.""GROUPS"" FOREIGN KEY(GROUP_OWNER_ID) REFERENCES PUBLIC.MEMBERS(MEMBER_ID) (12345)"; SQL statement:
insert into groups (id, description, group_name, group_owner_id) values (null, ?, ?, ?)
I am trying to figure out HOW I can do the mapping correctly, so that I can map the groupOwnerId from the URL. And also the new members gets saved into the database with auto generated IDs.
I am using H2 database at the moment but will eventually move to MySQL or Oracle.
Please let me know if you guys can help deciding the approach for this issue.
First of all, I would use the following URL patterns for the group and user resources:
/groups/{groupId}
/users/{userId}
This will allow you to create users and groups separately, since a group can exist without any users and a user can exist without being member of a group.
As usual a POST operation to /groups/ to create a group and POST to /users/ to create a user.
After having created a group and a user, I would retrieve both the entities (or use the entities returned by the POST operations) to set a group owner of the group and create a JSON (or XML) representation of the group.
The JSON representation is then PUT to the URL /groups/{groupId} (with the actual id of the group inserted in the URL) in order to persist the group with the new group owner.
Hope this helps!
I am using spring data jpa, spring boot and h2 database.
My repository looks like this
public interface IComponentStorageRepository extends JpaRepository<ComponentData, String> {
}
My domain object looks like this
#Entity
#Table(name = "component_data")
public class ComponentData {
#Column(name = "component_id")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String componentId;
private String description;
with overriden equals and hashcode.
I have a service method that looks like this.
#Override
public void updateComponent(String componentId, String description) {
if (repository.existsById(componentId)) {
ComponentData stored = repository.getOne(componentId);
stored.setDescription(description);
repository.saveAndFlush(stored);
} else {
ComponentData stored = new ComponentData();
stored.setComponentId(componentId);
stored.setDescription(description;
repository.saveAndFlush(newResult);
}
}
It firstly performs a check if the object exists (to prevent EntityNotFound exception) and then is trying to read one. If no object found it supposed to create a new one.
But each time I am trying to invoke the method - it is never able to find the entity. Every time it creates a new one.
I originally used repository.save and had #Transactional over the method but it didn't help either. I also haven't specified any properties and use everything default.
What is the problem
#GeneratedValue(strategy = GenerationType.IDENTITY) can't be used with String type and what i know it's not supported by the H2 inmemory DB.
When you want to use the key as String you have to assign it manually before persisting
without #GeneratedValue anotation or define the #GeneratedValue for String type properly.
There is an example from #VladMichalcea
https://vladmihalcea.com/hibernate-and-uuid-identifiers/ .
Another option would be to change the type of the primary key to be #GeneratedValue supported types
long (Long), int(Integer).
I found similar questions, but they did not answer my question.
I have two entities with a many-to-one relationship - unidirectional.
But most importantly, the relationship is lazy. Because it is correct to use a lazy connection, everyone knows it.
Code:
#Entity
public class User implements BaseEntity {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private City city;
}
#Entity
public class City implements BaseEntity {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
}
interface BaseEntity {
void setId(Long id);
Long getId();
}
I wrote a method that allows you to search by the transferred fields of the entity.
An example of how this works:
public class Search<T extends BaseEntity> {
public List<T> getByFields(T entity, List<FieldHolder> data) {
// create criteria with passed field name and value by reflection
}
}
class FieldHolder {
private String fieldName;
private Object value;
/**
* "true" - means that the field "value" contains id related object
* "false" - elementary type like: String, Wrapper, Primitive
*/
private boolean isRelationId;
}
The problem is that problems start when you need to search and related objects - by creating related queries.
The following entry is used to send the associated field: "city.id" and the problem is that when I transfer the essence of the related object (City) it is in a proxy and I cannot get id by reflection from City.
My function works perfectly if you specify:
#ManyToOne(fetch = FetchType.EAGER)
private City city;
But it will greatly affect performance, since I have a lot of related objects. Therefore, I want to solve this problem for a lazy load.
I know that this is not an easy task. But perhaps there is some opportunity to somehow get around this problem.
I have some trouble with Hibernate 4 and inheritance:
I use a ChildData class which inherit from BaseData by a JOIN inheritance strategy. My mapping is done by annotation in classes.
Everything is working fine except that when I delete a ChildData instance (with session.delete() or with a Hql query) the BaseData entry is also deleted.
I understand that in most case this is the awaited behavior, but for my particular case, I would like to preserve the BaseData entry no matter what for history purpose.
In other words I want all actions on the child class to be cascaded to base class except deletion.
I have already tried #OnCascade on the child class, with no success.
Is it a way to achieve this by code or do I have to use a SQL Trigger ON DELETE ?
EDIT :
Base Class
#Entity
#Table(name = "dbBenchHistory", uniqueConstraints = #UniqueConstraint(columnNames = "Name"))
#Inheritance(strategy = InheritanceType.JOINED )
public class DbBenchHistory implements java.io.Serializable {
private int id;
private String name;
private String computer;
private String eap;
private Date lastConnexion;
private Set<DbPlugin> dbPlugins = new HashSet<DbPlugin>(0);
private Set<DbSequenceResult> dbSequenceResults = new HashSet<DbSequenceResult>(
0);
public DbBenchHistory() {
}
public DbBenchHistory(int id, String name) {
this.id = id;
this.name = name;
}
public DbBenchHistory(int id, String name, String computer, String eap,
Date lastConnexion, Set<DbPlugin> dbPlugins,
Set<DbSequenceResult> dbSequenceResults) {
this.id = id;
this.name = name;
this.computer = computer;
this.eap = eap;
this.lastConnexion = lastConnexion;
this.dbPlugins = dbPlugins;
this.dbSequenceResults = dbSequenceResults;
}
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
//Getters/Setters
Child Class :
#Entity
#Table(name = "dbBench")
#OnDelete(action=OnDeleteAction.NO_ACTION)
public class DbBench extends DbBenchHistory {
private Set<DbProgram> dbPrograms = new HashSet<DbProgram>(0);
private Set<DbUser> dbUsers = new HashSet<DbUser>(0);
public DbBench() {
}
public DbBench(Set<DbProgram> dbPrograms,
Set<DbUser> dbUsers) {
this.dbPrograms = dbPrograms;
this.dbUsers = dbUsers;
}
//Getters/Setters
But I'm starting to think that I was wrong from the beginning and that inheritance was not the good way to handle this. If nothing shows up I will just go for BenchHistory - Bench being a simple one-to-one relationship
EDIT2 :
I edit while I can't answer my own question for insuficient reputation
I feel completly stupid now that I found the solution, that was so simple :
As I said, I was using hibernate managed methods : session.delete() or hql query. Hibernate was doing what he was supposed to do by deletintg the parent class, like it would have been in object inheritance.
So I just bypass hibernate by doing the deletion of the child class with one of the simplest SqlQuery on earth. And the base class entry remain untouched.
I understand that I somehow violate the object inheritance laws, but in my case it is really handy.
Thanks to everyone for your time, and believ me when I say I'm sorry.
I don't think Hibernate/JPA supports this. What you basically want is conversion from a subclass to a superclass, and not a cascading delete. When you have an object of the subclass, the members from the superclass are treated no different than the members of the subclass.
This can be solved through writing some logic for it though:
public void deleteKeepSuperclassObject(final ChildData childData) {
final BaseData baseDataToKeep = new BaseData();
//populate baseDataToKeep with data from the childData to remove
em.persist(baseDataToKeep);
em.remove(childData);
}