I have next JSON:
{
"name": "String",
"time": int,
"serve": int,
"type": "String",
"about": "String",
"userId": int,
"food": [{
"main_id": long
"name_Id": int,
"size": int,
"measure": "String",
"foodImgId": int
},
{
"main_id": long
"name_Id": int,
"size": int,
"measure": "String",
"foodImgId": int
}, ... ],
"steps": [{
"main_id": long
"step_id": int,
"step": "String",
"stepImgId": int
},
{
"main_id": long
"step_id": int,
"step": "String",
"img": int
}, ... ],
"img": [{
"main_id": long
"foodImgId": int,
"stepImgId": int,
"imgLink": "String"
},
{
"main_id": long
"foodImgId": int,
"stepImgId": int,
"imgLink": "String"
}, ... ],
}
And next models for this JSON:
#Entity
#Table(name = "MAIN")
public class Main implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "TIME", nullable = false)
private int time;
#Column(name = "SERVE", nullable = false)
private int serve;
#Column(name = "TYPE", nullable = false)
private String type;
#Column(name = "ABOUT", nullable = false)
private String about;
#Column(name = "USER_ID", nullable = false)
private int userId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Food> food;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Steps> steps;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "main_id", orphanRemoval = true)
private Set<Image> img;
// setter & getter
}
#Entity
#Table(name = "STEPS")
public class Steps implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "MAIN_ID", updatable = false)
public Main main_id;
#Column(name = "STEP_ID", nullable = false)
public int step_id;
#Column(name = "STEP", nullable = false)
public String step;
#OneToOne(optional = false)
#JoinColumn(name = "stepImgId", nullable = true)
public Image stepImgId;
// setter & getter
}
#Entity
#Table(name = "IMAGE")
public class Image implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "MAIN_ID", nullable = false)
private Main main_id;
#OneToOne(optional = false, mappedBy="foodImgId")
#Column(name = "foodImgId", nullable = true)
private Food food;
#OneToOne(optional = false, mappedBy="cookStepId")
#Column(name = "stepImgId", nullable = true)
private CookStep cookStepId;
#Column(name = "ImgLink", nullable = false)
private String imgLink;
// setter & getter
}
#Entity
#Table(name = "FOOD")
public class Food implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "MAIN_ID", nullable = false)
private Main main_idain_;
#Column(name = "NAME_ID", nullable = false)
private int nameId;
#Column(name = "SIZE", nullable = false)
private int size;
#Column(name = "MEASURE", nullable = false)
private String measure;
#Column(name = "foodImgId", nullable = true)
private int foodImgId;
// setter & getter
}
My question. How I can save that JSON to DB? Main_id, in each model, have to have id from Main class.
I have an empty repositories because I tried save data by default method repo.save(My_JSON), but i can not receive id from main class. I need any ideas, because I don't have enough experience with spring-boot.
Try this:
#JsonBackReference // add this to prevent Jackson (json) infinite recursion problem
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="MAIN_ID", referencedColumnName = "main_id" ,updatable = false,insertable = false)
public Main main_id;
Possible you forgot to add referencedColumnName which is the name of the corresponding column in the database.
I run your example in SprinBoot using Hibernate so I suggest in Food
class to change line
private Main main_idain_
to
private Main main_id
That was the error I run into.
OK guys, I found solution. My problem was what I couldn't get response which I need. I made one huge mistake. I did bidirectional connection and received loop and empty field. Finally my entities look next. I'll only show what changed.
In the main:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<FoodEntity> foodEntity;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<stepEntity> stepEntity;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "RECIPE_ID", referencedColumnName = "ID")
private Set<ImageEntity> imageEntity;
In another entities, I removed annotation #ManyToOne and left only row where I define variable(but it need not everywhere) where it need for query.
Related
enter image description here
I am trying to map some entities to tables in MySQL database using Spring Boot JPA. I have a problem with one of the tables because in that one too many foreign keys are added. I highlighted the columns in the picture. I suppose that the problem might be linked with the fact that the Tutorial table has either One to Many or Many to Many relations with the other 3 tables, but I am not sure
#Entity(name = "authors")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "author_id")
private Long authorId;
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Column(name = "email", length = 320, unique = true)
private String email;
#Column(name = "job_title", length = 255)
private String jobTitle;
#Lob
#Type(type = "org.hibernate.type.BinaryType")
#Column(name = "profile_picture")
private byte[] profilePicture;
#Column(name = "about", length = 2000)
private String about;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private List<Tutorial> tutorials;
}
#Entity(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Long categoryId;
#Column(name = "category_name", nullable = false, unique = true, length = 100)
private String categoryName;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id")
private List<Tutorial> tutorials;
}
#Entity(name = "tutorials")
public class Tutorial {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tutorial_id")
private Long tutorialId;
#Column(name = "tutorial_title", nullable = false, length = 150)
private String tutorialTitle;
#Column(name = "tutorial_description", nullable = false, length = 2000)
private String tutorialDescription;
#Column(name = "time_to_complete")
private Integer timeToComplete;
#Column(name = "date_published")
private Long datePublished;
#Column(name = "last_updated")
private Long lastUpdated;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tutorials")
private List<User> users = new ArrayList<>();
#ManyToOne(fetch = FetchType.EAGER)
private Category category;
#ManyToOne(fetch = FetchType.EAGER)
private Author author;
}
Tutorials is the table where the problems appear as 4 foreign keys are generate instead of two
#Entity(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "first_name", nullable = false, length = 100)
private String firstName;
#Column(name = "last_name", nullable = false, length = 100)
private String lastName;
#Column(name = "user_name", nullable = false, unique = true, length = 100)
private String userName;
#Column(name = "age")
private Integer age;
#Column(name = "email", length = 320, unique = true)
private String email;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "users_tutorials",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "tutorial_id") })
private List<Tutorial> tutorials = new ArrayList<>();
}
Try this changes:
remove #JoinColumn(name = "author_id")from Author and place in Tutorial:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "author_id")
private Author author;
remove #JoinColumn(name = "category_id")from Category and place it in Tutorial as well:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category_id")
private Author author;
To get more information look here: Baeldung - Hibernate One to Many
I have 4 entities: workflowEntity, PhaseEntity, RoundEntity and BlockEntity.
The structure is supposed to be following:
Workflow:
Phase:
Round(optional):
Block
or if there are no rounds, since round_id in workflow_binding table is nullable;
Workflow:
Phase:
Block
I need workflowEntity, when bound with Phase, Block or Round entities, to hold the information of those entitites, but those other entites should not contain any information of their "structural children". e.g I request getAllPhases(), then those Phases that get returned, should not contain information of any Blocks nor Rounds, because unless phases are bound within workflow, they are not tied with Blocks nor Rounds in any way. They can only be tied with Blocks and Rounds in bound workflowEntity.
I have tried to use #JoinTable (commented out examples in workflowEntity, PhaseEntity and RoundEntity), but this returned too much data, that wasnt bound to requested workflow.
Basically I need WorkflowEntity to contain fields such as:
private Map<PhaseEntity, Map<RoundEntity, Set<BlockEntity>>> phaseRoundEntityMap;
private Map<PhaseEntity, Set<BlockEntity>> phaseBlockEntityMap;
#Entity
#Data
#Table(name="workflow")
public class WorkflowEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id")
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
#Column(name= "name")
private String name;
#Column(name="days")
private Integer days;
/*
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_phase_id")})
private Set<PhaseEntity> phases;*/
//I need to map these somehow
private Map<PhaseEntity, Map<RoundEntity, Set<BlockEntity>>> phaseRoundEntityMap;
private Map<PhaseEntity, Set<BlockEntity>> phaseBlockEntityMap;
}
#Entity
#Data
#Table(name="workflow_phase")
#NoDuplicatePhases
public class PhaseEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
#Size(max = 1500)
private String description;
private Integer sequenceNumber;
// Phase shouldnt actually contain any information about blocks nor rounds, unless bound with a workflow.
/* Commented out, because includes blocks and rounds not bound to requested workflow
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_phase_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_block_id")})
private Set<BlockEntity> blocks;
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_phase_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_round_id")})
private Set<RoundEntity> rounds;*/
}
#Entity
#Data
#Table(name="workflow_round")
#NoDuplicateRounds
public class RoundEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
private Integer sequenceNumber;
/*Commented out, because includes blocks not bound to requested workflow
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_round_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "workflow_block_id", nullable = false, updatable = false)})
private Set<BlockEntity> blocks;*/
}
#Entity
#Data
#Table(name = "workflow_block")
public class BlockEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
#Size(max = 1500)
private String description;
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.MERGE}, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_assignment_block",
joinColumns = {#JoinColumn(name = "workflow_block_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "workflow_assignment_id")})
private Set<AssignmentEntity> assignments;
private Integer sequenceNumber;
#Column(name = "due_date_day")
private Integer dueDate;
}
Why not create an entity WorkflowBinding?
#Entity
#Table(name = "workflow_binding")
public class WorkflowBinding {
#EmbeddedId
private WorkflowBindingId id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "block_id", insertable = false, updatable = false)
private BlockEntity block;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "round_id", insertable = false, updatable = false)
private RoundEntity round;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "phase_id", insertable = false, updatable = false)
private PhaseEntity phase;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "workflow_id", insertable = false, updatable = false)
private WorkflowEntity workflow;
}
#Embeddable
public class WorkflowBindingId {
private UUID blockId;
private UUID roundId;
private UUID phaseId;
private UUID workflowId;
}
On the inverse side you can then do mappings like this:
public class BlockEntity {
#OneToMany(mappedBy = "block")
private Set<WorkflowBinding> bindings;
....
}
i have 2 models User and roles , its a many to many relation ship.
i need to add a user and give him a specific role already present in my data base.
------User------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_Id")
private int userId;
#Column(name="name")
private String name;
#Column(name="lastname")
private String lastname;
#Column(name="email")
private String email;
#Column(name="password")
private String password;
#Column(name="isActive")
private boolean isActive;
#Column(name="lastActive")
private String lastActive;
#Column(name="createdDate")
private String createdDate;
#Column(name="isBlocked")
private boolean isBlocked;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "institution_id", nullable = false)
#JsonIgnoreProperties(value = {"user"})
private Institution institution;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH})
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();
}
--------Roles--------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="role_Id")
private int roleId;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"roles"})
private Set<User> users = new HashSet<>();
}
and the application's controller
#PostMapping("/addUser")
public String addUser(#RequestBody User user) {
userrepository.save(user);
return "user saved with name: " + user.getName();
}
and this is the json body i send with the api request.
{
"userId" : 7,
"name": "test",
"lastname": "testlast",
"email": "testtest#yahoo.com",
"password": "test123",
"lastActive": "04/05/21",
"createdDate": "02/04/20",
"institution": {
"institutionId": 4
},
"roles": [
{
"roleId": 2
}
],
"active": false,
"blocked": true
}
everything worls just fine to my user-has-role table a record is added with the userId = 7 and roleId=2
but the problem is that the table role is getting updated and the fields name and description are getting erased and replaced by null values...
any ideas please
You have added CascadeType.PERSIST to User and Role #ManyToMany join.
When the User entity is persisted to the EntityManager, it will also persist the Role entity. As you are passing the primary key in the request payload for Role it will create/update the Role table.
You need to remove the CascadeType.PERSIST from joining and it will work as expected.
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH })
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();
I have a LibraryModel class, a LibraryImage class and a LibraryAttribute class. A LibraryModel can have an arbitrary number of LibraryImages and LibraryAttributes.
The error that I get:
org.hibernate.MappingException: Foreign key (FKmbn4xh7xdxv371ao5verqueu3:library_item_attribute [LIBRARY_ITEM_ATTRIBUTE_ID])) must have same number of columns as the referenced primary key (library_item_attribute [LIBRARY_ITEM_ID,LIBRARY_ITEM_ATTRIBUTE_ID])
Here are my annotated Objects:
Library Model:
#Entity
#Table(name = "library_item", uniqueConstraints = {
})
#Inheritance(strategy = InheritanceType.JOINED)
public class LibraryItemModel implements LibraryItem{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ID", unique = true, nullable = false)
private Integer libraryItemId;
#Column(name = "ITEM_TITLE", unique = false, nullable = false)
private String itemTitle;
#Column(name = "IS_PARENT", nullable = false)
private Boolean isParent;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ID", nullable = false)
private LibraryModel libraryModel;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "ITEM_LISTING",
joinColumns = {#JoinColumn(name = "PARENT_LIB_ITEM_ID", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="CHILD_LIB_ITEM_ID", nullable = false)})
private Set<LibraryItemModel> itemChildren = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "itemChildren")
private Set<LibraryItemModel> itemParents = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_IMAGE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable=false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_IMAGE_ID", nullable = false)})
private Set<LibraryImage> itemImages = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "rootLibraryItemModel")
private Set<LibraryModel> libraries = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_ATTRIBUTE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable =false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_ITEM_ATTRIBUTE_ID", nullable = false)})
private Set<LibraryItemAttribute> libraryItemAttributes = new HashSet<>();
}
LibraryImage:
#Entity
#Table(name = "library_image", uniqueConstraints = {
})
public class LibraryImage {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_IMAGE_ID", unique = true, nullable = false)
private Integer libraryImageId;
#Column(name = "IMAGE_LOCATION")
private String imageLocation;
#Column(name = "IMAGE_TITLE")
private String imageTitle;
#Enumerated(EnumType.STRING)
private LibraryImageType imageType;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
Library Attribute:
#Entity
#Table(name = "library_item_attribute", uniqueConstraints = {
})
public class LibraryItemAttribute {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ATTRIBUTE_ID", unique = true, nullable = false)
private Integer libraryItemAttributeId;
#Column(name = "ATTRIBUTE_NAME")
private String attributeName;
#Column(name = "ATTRIBUTE_VALUE")
private String attributeValue;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
This is really frustrating as the LibraryImage class is mapped without problems and doesn't throw any errors, but the LibraryAttribute class which is annotated in an identical way to the LibraryImage class is throwing this error.
Can someone please have a look and let me know what my problem is?
Found the problem.
In the LibraryItemModel class I defined the Join table with the LibraryItemAttribute to be called "LIBRARY_ITEM_ATTRIBUTE", which is the name of the table of the Library item attributes.
The join table is a different table and should have a different table name.
For the Library Image table above, the image table is called library_image, while the join table is called LIBRARY_ITEM_IMAGE
Ok, so I'd like to implement a simple forum example. So, I have threads, messages and users, of course and these are the pojos (I omitted the usually getters and simplicity)
Message
#Entity
#Table(name = "message")
public class Message implements java.io.Serializable, RecognizedServerEntities
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name = "thread", nullable = false)
private Thread thread;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name = "author", nullable = true)
private User user;
#Column(name = "title", nullable = false, length = 31)
private String title;
#Column(name = "body", nullable = false, columnDefinition = "Text")
private String body;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_modified_date", nullable = false, length = 19)
private Date lastModifiedDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date", nullable = false, updatable = false, length = 19)
private Date createdDate;
}
User
#Entity
#Table(name = "user", uniqueConstraints =
{ #UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "nick") })
public class User implements java.io.Serializable, RecognizedServerEntities
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "email", unique = true, nullable = false, length = 31)
private String email;
#Column(name = "password", nullable = false, length = 31)
private String password;
#Column(name = "nick", unique = true, nullable = false, length = 31)
#NaturalId(mutable = false)
private String nick;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "registered_date", nullable = false, updatable = false, length = 19)
private Date registeredDate;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
private Set<Thread> threads = new HashSet<Thread>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
private /**transient /**/ Set<Message> messages = new HashSet<Message>(0);
}
Thread
#Entity
#Table(name = "thread")
public class Thread implements java.io.Serializable, RecognizedServerEntities
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade({CascadeType.SAVE_UPDATE})
#JoinColumn(name = "parent_thread", nullable = true)
private Thread parentThread;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade({CascadeType.SAVE_UPDATE})
#JoinColumn(name = "author", nullable = true)
private User user;
#Column(name = "title", nullable = false, length = 63)
private String title;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_modified_date", nullable = false, length = 19)
private Date lastModifiedDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date", nullable = false, updatable = false, length = 19)
private Date createdDate;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread"/**/, orphanRemoval = true/**/)
#Cascade({ CascadeType.REMOVE })
private /**transient /**/ Set<Message> messages = new HashSet<Message>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parentThread", orphanRemoval = true)
#Cascade({CascadeType.REMOVE })
private /**transient /**/ Set<Thread> subThreads = new HashSet<Thread>(0);
}
I have many doubts on the annotations of course, but these are the relevant choice.
When I delete an user, I don't want to delete all his threads and messages, so it make sense to don't use orphan-removal or cascade delete on the #OneToMany associations (ie the messages and threads collections).
Also, because the id is automatically generated from the database, I don't think it make sense at all to use the annotation CascadeType.UPDATE (or SAVE_UPDATE) on the collections of all the entity.
A thread are the most problematic entity to manage. When we delete a thread, we want that all its subthreads and all its messages were deleted. So, I use the CascadeType.REMOVE and orphan-removal annotations.
An all the #ManyToOne associations, I use the CascadeType.ALL. The idea is that if we delete a message or a subthread, all the parents will be updated.
All the collections are not transient.
Feel free to propose suggestion on this of course.
Btw, given the whole story, this is the question: suppose I have a thread "mThread" started from the user "mUser" with many messages from different users, how can I safely delete the user?
I tried different things, but I'm not sure of anything and in most cases I only have exceptions.
EDIT
I also have another class, StorageManager<T>, that is used to encapsulate the common code between entities. Basically, it implements the "one session per transaction" pattern. So each methodX() basically:
invoke sessionFactory.openSession() and session.beginTransaction()
invoke session.methodX()
invoke transaction.commit()
invoke session.clear() and session.close
Example with code
for (Thread t : mUser.getThreads())
{
t.setUser(null);
storageManagerThread.update(t);
}
for (Message m : mUser.getMessages())
{
m.setUser(null);
storageManagerMessage.update(t);
}
storageManagerUser.delete(mUser);
Until this point, all the table in the database have the right values. However, I don't know if it is the right way to proceed, because it leaves dirty collections.
Indeed, when at later point I try to execute some other options (e.g. update(mThread) or delete a message from mThread) a NullPointerException was thrown. Why is this? .