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? .
Related
I'm having an issue with JPA and Hibernate. I have 2 classes (actually more but those are the 2 that are giving me a headache).
They have a OneToMany/ManyToOne relationship, and I have specified on the OneToMany that I want it to cascade every possible change (CascadeType.ALL).
However, when I let HBM2DDL run in "update" mode, it is creating foreign key constraints that are not fitting what I want to achieve. It is creating a constraint in RESTRICT mode, which is absolutely not what I have specified. Perhaps the error is in my code ? I've joined it below.
#Entity
public class Department {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "departmentByDepartment", cascade = CascadeType.ALL)
private Collection<Article> productsById;
#OneToMany(mappedBy = "departmentByDepartment", cascade = CascadeType.ALL)
private Collection<User> usersById;
And then :
#Entity
#Table(name = "product", schema = "qlog_project", catalog = "")
public class Article {
#Id
#Column(name = "id", nullable = false)
private int id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Column(name = "price", nullable = false)
private double price;
#Column(name = "image", nullable = false)
private String image;
#ManyToOne
#JoinColumn(name = "department", referencedColumnName = "id", nullable = false)
private Department departmentByDepartment;
#OneToMany(mappedBy = "productByProductId", cascade = CascadeType.ALL)
private Collection<Stock> stocksById;
Also, I cannot seem to be able to delete an Article instance with EntityManager.remove(). It's not producing any query and not doing anything. Maybe it is linked ?
Thanks in advance,
Regards
I did googled a lot, still dont find any solution hence posting a question here..
I am developing Many-To-Many relationship example using lombok. I just want to create argument constructor for only two fields out of four. How we can do that ?
#Data
#Entity
#Table(name = "stock")
public class Stock implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "STOCK_ID", unique = true, nullable = false)
private Integer stockId;
#Column(name = "STOCK_CODE", unique = true, nullable = false, length = 10)
private String stockCode;
#Column(name = "STOCK_NAME", unique = true, nullable = false, length = 20)
private String stockName;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "stock_category", joinColumns = {
#JoinColumn(name = "STOCK_ID", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "CATEGORY_ID", nullable = false, updatable = false)})
private Set<Category> categories = new HashSet<Category>(0);
}
Category
#Data
#RequiredArgsConstructor(staticName = "of")
#Entity
#Table(name = "category")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CATEGORY_ID", unique = true, nullable = false)
private Integer categoryId;
#Column(name = "NAME", nullable = false, length = 10)
#NonNull
private String name;
#Column(name = "[DESC]", nullable = false)
#NonNull
private String desc;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private Set<Stock> stocks = new HashSet<Stock>(0);
}
App.java
Why cant I set the limitted field constructor
public class App {
public static void main(String[] args) {
System.out.println("Hello World!");
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
Stock stock = new Stock();
stock.setStockCode("7052");
stock.setStockName("PADINI");
Category category1 = new Category("CONSUMER", "CONSUMER COMPANY");
Category category2 = new Category("INVESTMENT", "INVESTMENT COMPANY");
Set<Category> categories = new HashSet<Category>();
categories.add(category1);
categories.add(category2);
stock.setCategories(categories);
session.save(stock);
session.getTransaction().commit();
System.out.println("Done");
}
}
The reason is that
If staticName set, the generated constructor will be private, and an additional
static 'constructor' is generated with the same argument list that
wraps the real constructor.
Please, don't forget about #NoArgsConstructor because Hibernate needs it.
[using JPA, MySQL, MVC, Servlets, JSP]
If I read some data from database LEFT JOIN-ing three tables (inside method of DAO object) how should I format that result, so i can set it as request attribute (in servlet) and forwards to JSP page?
Entities(tables in db):
Post:
#Entity
#Table(name = "post")
public class Post implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "post_id", unique = true, nullable = false)
private Integer id;
#Column(name = "post_title", length=300, unique = false, nullable = false)
private String title;
#Column(name = "post_date", unique = false, nullable = false)
private Date date;
#Column(name = "post_summary", length=1000, unique = false, nullable = true)
private String summary;
#Column(name = "post_content", length=50000, unique = false, nullable = false)
private String content;
#Column(name = "post_visitors", unique = false, nullable = false)
private Integer visitors;
#ManyToOne
#JoinColumn (name = "user_id", referencedColumnName="user_id", nullable = false)
private User user;
#ManyToOne
#JoinColumn (name = "category_id", referencedColumnName="category_id", nullable = false)
private Category category;
#OneToMany(cascade = { ALL }, fetch = LAZY, mappedBy = "post")
private Set<Comment> comments = new HashSet<Comment>();
...
Entity Comment:
#Entity
#Table(name = "comment")
public class Comment implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "comment_id", unique = true, nullable = false)
private Integer id;
#Column(name = "comment_title", length=300, unique = false, nullable = false)
private String title;
#Column(name = "comment_date", unique = false, nullable = false)
private Date date;
#Column(name = "comment_content", length=600, unique = false, nullable = false)
private String content;
#ManyToOne
#JoinColumn (name = "user_id", referencedColumnName="user_id", nullable = false)
private User user;
#ManyToOne
#JoinColumn (name = "post_id", referencedColumnName="post_id", nullable = false)
private Post post;
...
Entity User:
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
private Integer id;
#Column(name = "user_name", length=45, unique = false, nullable = false)
private String name; // "first_name" + ' ' + "last_name"
//URL address to user's image
#Column(name = "user_image", length=500, unique = false, nullable = true)
private String image;
#Column(name = "user_username", length=45, unique = false, nullable = false)
private String username;
#Column(name = "user_password", length=45, unique = false, nullable = false)
private String password;
...
So, I would like to make a method, probably inside PostDAO object that will look something like this:
public <SomeDataTypeFormat???> getPostsSummaries(){
Query q = em.createNativeQuery("SELECT
post_title,
post_summary,
post_date,
COUNT(comment_id) AS comment_cnt,
user.user_name
FROM
post
LEFT JOIN user USING(user_id)
LEFT JOIN comment USING(post_id)
GROUP BY
post_id
ORDER BY
comment_cnt DESC");
...
}
Method returns some fields from all three tables in database.
Do I need to make separate class and store those data in objects of that class? Or JSON (although I haven't worked with it yet)?
What is the practice? What is easiest data format to use and forward from servlet to JSP, for some fields gotten as a result of joining couple tables?
It depends on your objective; to get the data to the browser, JSON and AJAX isn't a bad choice. To get the data to the JSP (from the Servlet), you'll probably want a Data Transfer Object (or possibly an immutable Value Object).
I'm having trouble with a one to many relationship. I have a user, user_company (relates a user to a company, a user can have several companies) and a user role (a user can have different roles in different companies). The problem is that the user role it's only loaded with the first user. If I test any other user the user role is null. It's really weird, I have 10 users and only the first one works. Here is the code:
User:
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 14)
#Column(name = "user_name")
private String userName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 14)
#Column(name = "user_password")
private String userPassword;
#Size(max = 1)
#Column(name = "user_logged")
private String userLogged;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "userCompanyUserName")
private Collection<UserCompany> userCompanyCollection;
User Company:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "user_company_id")
private Integer userCompanyId;
#JoinColumn(name = "user_company_user_name", referencedColumnName = "user_name")
#ManyToOne(optional = false)
private Users userCompanyUserName;
#JoinColumn(name = "user_company_company", referencedColumnName = "company_id")
#ManyToOne(optional = false)
private Companies userCompanyCompany;
/*
#JoinColumns({
#JoinColumn(name = "user_company_user_roles", referencedColumnName = "user_role_id"),
#JoinColumn(name = "user_company_id", referencedColumnName = "user_role_company_id", insertable = false, updatable = false)
})*/
#JoinColumns({
#JoinColumn(name = "user_company_user_roles", referencedColumnName = "user_role_id"),
#JoinColumn(name = "user_company_id", referencedColumnName = "user_role_company_id", insertable = false, updatable = false)
})
#ManyToOne(optional = false)
private UserRoles userCompanyUserRoles;
User Roles:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "userCompanyUserRoles")
private Collection<UserCompany> userCompanyCollection;
private static final long serialVersionUID = 1L;
#EmbeddedId
protected UserRolesPK userRolesPK = new UserRolesPK();
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(name = "user_role_name")
private String userRoleName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 200)
#Column(name = "user_role_description")
private String userRoleDescription;
About the data:
I have 10 users, called: test1,test2,test3....
I have 10 records of user_company. They relate all the users to company1 and userrole1.
The data is basically the same for the 10, that's why I think is really weird that only test1 works.
Thanks in advance for all the help.
Regards,
Daniel
Enable logging a check the SQL generated. If it is correct, execute it directly on your database, see if you get the result you expect.
Do you create the data in your test, or is it existing? It could be you are corrupting your cache when creating the data. Ensure you correctly maintain your bi-directional relationships.
I have a M:N relationship between table Users and Groups. Now I am trying to join those two tables using JPA and I always get this exception:
Multiple writable mappings exist for the field [GROUPS.name]. Only one may be defined as writable, all others must be specified read-only.
Here is my Users class (I didn't enclose getters and setters for brevity), it's implemented by Admin class and SignedUser class with some additional properties.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Users implements Serializable {
#Id
#Column(name = "login", nullable = false, length = 10)
private String login;
#Column(name = "name", nullable = false, length = 30)
private String name;
#Column(name = "surname", nullable = false, length = 50)
private String surname;
#Column(name = "email", nullable = false, length = 100)
private String email;
#Column(name = "password", nullable = false, length = 20)
private String password;
#ManyToMany(mappedBy="users")
private List<Groups> groups;
and this is the Group class:
#Entity
public class Groups implements Serializable {
#Id
#Column(name = "name", nullable = false, length = 20)
private String groupName;
#Column(name = "name", nullable = false, length = 50)
private String descr;
#ManyToMany
#JoinTable(name = "user_group",
joinColumns = {#JoinColumn(name = "groupName")},
inverseJoinColumns = {#JoinColumn(name = "login")})
private List<Users> users;
I've also tried to put JoinTable annotation into Users class but it ended with the same result. Thanks in advance for any advices.
In the class Group you have two mappings for column name as properties groupName and descr.
The error is not related to the ManyToMany.