I am making an application with users. A user has an id, which is automatically assigned using the following Hibernate annotation:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id
That is all fine and dandy, however the problem is that every time I run the application (while adding the same user) it assigns a new id to the user, regardless if the user already exists.
What can I do that makes it so an existing user doesn't get a new id every time I run the application without having to request all users to check their username individually?
Thanks in advance.
EDIT
Some more code examples (I will only write relevant code here).
The User:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
// more code here
}
The User hibernate class with the save function:
(The session is opened in Main and given to the constructor).
public class UserDAOHibernate implements GebruikerDAO {
private Session session;
public UserDAOHibernate(Session session) {
this.session = session;
}
#Override
public boolean save(User user) {
try {
if (this.findById(user.getId()) == null) {
Transaction transaction = session.beginTransaction();
session.save(user);
transaction.commit();
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
// more code here
}
The above code where I check if the user already exists based on the id doesn't work because of the fact that the user keeps getting new ids. So this function checks if there exists a user with id = 2, while the one that already exists has id = 1.
In Main:
// lots of code
User user = new User("Stijuh");
// more code here
UserDAOHibernate udao = new UserDAOHibernate(session);
udao.save(user);
When you want to update, before saving the object, you have to set the id field. This way Hibernate will not generate a new one
Related
In my app, I have a many-to-many association between the User and Preference entities. Since the join table requires an additional column, I had to break it down into 2 one-to-many associations as such:
User entity :
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade={CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
public Set<UserPreference> getPreferences()
{
return preferences;
}
Preference entity :
#OneToMany(mappedBy = "preference", fetch = FetchType.EAGER)
public Set<UserPreference> getUserPreferences()
{
return userPreferences;
}
UserPreference entity :
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
public User getUser()
{
return user;
}
public void setUser(User user)
{
this.user = user;
}
#ManyToOne
#JoinColumn(name = "preference_id", nullable = false)
public Preference getPreference()
{
return preference;
}
public void setPreference(Preference preference)
{
this.preference = preference;
}
#Column(nullable = false, length = 25)
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
To update one of the preferences, I loop through the user's set of preferences and update the value as such:
#RequestMapping(value = {"/edit-{id}-preference"}, method = RequestMethod.POST)
public String updateUserPreference(#ModelAttribute("userPreference") UserPreference preference, BindingResult result, ModelMap model)
{
User loggedInUser = (User)session.getAttribute("loggedInUser");
for (UserPreference pref : loggedInUser.getPreferences())
{
if (Objects.equals(pref.getId(), preference.getId()))
{
pref.setValue(preference.getValue());
}
}
userService.update(loggedInUser);
return "redirect:/users/preferences";
}
I have confirmed that the user variable I'm trying to update does indeed contain the new value after this code runs. Even weirder, the value does update on the webpage when the redirect happens but the database does NOT update! This is the code I'm using to do the update, this class is annotated with #Transactional and every other call to this method (to update the user's role for example) works perfectly:
#Override
public void update(User user)
{
User entity = dao.findById(user.getId());
if (entity != null)
{
entity.setUserId(user.getUserId());
entity.setPassword(user.getPassword());
entity.setFirstName(user.getFirstName());
entity.setLastName(user.getLastName());
entity.setRole(user.getRole());
entity.setPreferences(user.getPreferences());
}
}
This acts like hibernate's session "cache" has the updated value but does not actually persist it. I am using this very same update method style for about 30 other entities and everything works fine. This is my only many-to-many association that I had to break down into 2 one-to-many associations so I have nothing to compare to.
Am I doing something wrong? When I create a user with a new HashSet and persist it, the value is written correctly in the "join table".
*****EDIT*****
For comparison, this is the code I use to create a new user with default preferences. The preferences exist already but the join table is completely empty and this code correctly persists the entities:
User user = new User();
user.setUserId("admin");
user.setPassword(crypter.encrypt("admin"));
user.setFirstName("admin");
user.setLastName("admin");
user.setRole(roleService.findByName("Admin"));
Set<UserPreference> userPreferences = new HashSet<>();
Preference preference = preferenceService.findByName("anchorPage");
UserPreference userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("System Statistics");
userPreferences.add(userPreference);
preference = preferenceService.findByName("showOnlyActivePatients");
userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("true");
userPreferences.add(userPreference);
user.setPreferences(userPreferences);
userService.save(user);
Thanks
Instead of
entity.setPreferences(user.getPreferences());
Do something like:
for( UserPreference uf : user.getPreferences() ) {
entity.getPreferences().add( uf );
}
The main difference here is that you aren't changing the list reference, which is managed by Hibernate, and is only adding elements to it.
How about using merge? That is what you are cascading after all and you have modified a detached object and need to merge back the changes:
public void update(User user) {
dao.merge(user);
}
EDIT: for clarity this replaces the old update method, so it should be called from the client side with loggedInUser, just like before.
EDIT 2: as noted in the comments merge will update all fields. The old update method also seems to do that? Optimistic locks (version numbers) can be used to guard against overwriting other changes by mistake.
I have two objects User and Workorder. One user can have multiple work orders. The problem is when I delete user it also deletes assigned work orders to that user. I have tried to set my work orders foreign keys to NULL before deleting the user but it still deletes all the associated work orders with that user. I'd like to delete user without deleting the work order assigned to user. What am I missing or doing wrong?
Here's is my User class:
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", orphanRemoval=true)
private Set<WorkOrder> workOrder;
WorkOrder class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id", nullable = true)
private User user;
UserDAOImpl class:
#Override
public void deleteUser(int theId) {
// get the current hibernate session
Session currentSession = sessionFactory.getCurrentSession();
// delete object with primary key
User user = currentSession.get(User.class, theId);
Set workorders = user.getWorkOrder();
Iterator<WorkOrder> work = workorders.iterator();
while (work.hasNext()){
WorkOrder workorder = work.next();
workorder.setUser(null);
}
currentSession.remove(user);
}
Remove that 'orphanRemoval=true' and check there's no 'cascade' on Workorder.user (if the relation is bidirectional)
is there a tutorial for a cost-effective entity design?
If I make a small sample and say I want to store users and groups. These groups have a List of users. If any user wants to join this group I have to check is this group existing and the user isn't part of the group.
My question is not how to do this. The question is for a good entity-design or a good objectify-using.
Here is some shortened sample code how I would do this:
User.java
#Entity
#Cache
#Embed
public class User {
#Id Long id;
#Index String name;
String passwordHash;
}
Group.java
#Entity
#Cache
public class Group {
#Id Long id;
#Index Long groupAdministratorUserId;
#Index String name;
List<User> users = new ArrayList<User>();
#Index Boolean isPublic;
}
using
if (!app.authenticate(getRequest(), getResponse()))
{
// Not authenticated
setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
}
else
{
Group newGroup = ofy().load().type(Group.class).id(Long.parseLong(id)).now(); // is it correct that the embedded data is already loaded?
// following check and insert is only for illustration!
newGroup.getUsers().contains(connectedUser);
newGroup.getUsers().add(connectedUser);
ofy().save().entity(newGroup).now();
}
My "overhead" (authentication)
public class MyVerifier extends LocalVerifier {
private User fetched;
public User getFetched() {
return fetched;
}
#Override
public char[] getLocalSecret(String identifier) {
// this is behind search... and another list()
// User fetched = ofy().load().type(User.class).filter("name", userName).first().now();
fetched = User.searchByExactName(identifier);
if (fetched != null)
{
return fetched.getPasswordHash().toCharArray();
}
return null;
}
}
P.S. I know the page from google: https://code.google.com/p/objectify-appengine/wiki/BestPractices
But that is not what I'm searching for
I would store the list of groups IDs the User entity. No need to use #Embed. What the best solution is really depends on what the most common operations will be in your application. Based on what you said I would recommend the following:
#Entity
#Cache
public class User {
#Id long id;
String name;
String passwordHash;
List<Long> groups;
// constructor left out for brevity.
}
#Entity
#Cache
public class Group {
#Id long id;
long adminId;
String name;
boolean isPublic;
// constructor left out for brevity.
}
User user1 = new User(userName1, passwordHash1);
User user2 = new User(userName2, passwordHash2);
Key<User> user1Key = ofy().save().entity(user1).now(); // Create two users.
Key<User> user2Key = ofy().save().entity(user2).now(); // The don't have any groups yet.
long adminId = user1Key.getId();
Group group = new Group(name, adminId, isPublic)
Key<Group> groupKey = ofy().save().entity(group).now(); // Create a group
user2.addToGroup(groupKey.getId()); // This adds the group ID to the User.groups list.
ofy().save().entity(user2).now(); // Add user2 to group.
To save costs (specifically on small datastore operations during updates) make sure to create as few indexes as possible. Start with few #Indexes and add them as necessary.
I'm using JPA2 with Eclipselink implementation on Glassfish 3.
I have a simple login/logout application.
These are the entity beans an their mappings:
#Entity
#Table(name = "user_account")
public class UserAccount implements Serializable{
#Id
private Integer id;
private String email;
private String password;
private Integer role;
#OneToOne(fetch=FetchType.EAGER)
#PrimaryKeyJoinColumn
private UserDetail userDetail;
// get+set
}
#Entity
#Table(name = "user_detail")
public class UserDetail implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private Integer id;
#Temporal(TemporalType.DATE)
private Date birth;
#Column(name="origin_city")
private String originCity;
#Column(name="residence_city")
private String residenceCity;
private String description;
#OneToOne(mappedBy="userDetail")
private UserAccount userAccount;
}
In a UserService class I have a simple CRUD implementation. All the methods work just fine but the problem is that sometimes the Entity Object saved in the Persistance Context / JPA Cache doesn't synchronize with the database information.
As a specific example, when I delete some user details (from the user_detail table) the database entry is deleted but when I read the information again, the returned entity still has the Details information. I'm guessing that this info was kept in the JPA cache and brought back without checking the database first.
Is this the correct behaviour? Is there a way to keep the Cache information synchronized with the database?
LE : The user Service contains the read(email, password) method which calls a helper method. This helper method contains the CriteriaAPI query which brings all the users with the desired email and password. The Criteria query is tested and works fine.
public UserAccount read(String email, String password){
List<UserAccount> userList = getUserList(null, email, password, Constants.RESULT_SINGLE);
return (userList.isEmpty() ? null : userList.get(0));
}
private List<UserAccount> getUserList(Integer id, String email, String password, String resultType){
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<UserAccount> shell = builder.createQuery(UserAccount.class);
Root<UserAccount> entity = shell.from(UserAccount.class);
shell.select(entity);
shell.distinct(true);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (id != null){
ParameterExpression<Integer> param = builder.parameter(Integer.class, "id");
predicateList.add(builder.equal(entity.get("id"), param));
}
if (email != null){
ParameterExpression<String> param = builder.parameter(String.class, "email");
predicateList.add(builder.equal(entity.get("email"), param));
}
if (password != null){
ParameterExpression<String> param = builder.parameter(String.class, "password");
predicateList.add(builder.equal(entity.get("password"), param));
}
if (predicateList.size() == 1){
shell.where(predicateList.get(0));
} else {
Predicate[] p = new Predicate[predicateList.size()];
p = predicateList.toArray(p);
shell.where(builder.and(p));
}
TypedQuery<UserAccount> selectQuery = em.createQuery(shell);
if (id != null) selectQuery.setParameter("id", id);
if (email != null) selectQuery.setParameter("email", email);
if (password != null) selectQuery.setParameter("password", password);
return selectQuery.getResultList();
}
This is the delete method from the service:
public boolean deleteDetail(Integer userId) {
boolean ok = true;
try {
UserDetail userDetails = em.find(UserDetail.class, userId);
System.out.println("Trying to delete the detail. The address of the object is: " + userDetails);
em.remove(userDetails);
System.out.println("Object deleted. The address is: " + userDetails);
} catch (Exception e) {
e.printStackTrace();
ok = false;
}
return ok;
}
Reading the comments I see that you are using application-managed transactions. As stated in the Java EE Tutorial,
Application-managed entity managers don’t automatically propagate the
JTA transaction context. Such applications need to manually gain
access to the JTA transaction manager and add transaction demarcation
information when performing entity operations. The
javax.transaction.UserTransaction interface defines methods to begin,
commit, and roll back transactions. Inject an instance of
UserTransaction by creating an instance variable annotated with
#Resource...
From the code you have posted, you are not demarcating the transaction that removes the UserDetail record. Moreover, you have not shown the code that you are using for getting the EntityManager reference. You should stick to the same tutorial and use
em = emf.createEntityManager();
for getting an updated instance of the Entity Manager.
Otherwise, switch to Container-Managed transactions that make life much simpler in most of the situations.
I currently am trying to persist a collection using #OneToMany(cascade=CascadeType.ALL) for a simple list of objects. The table for Parent_Child gets created in MySQL but the keys for each object are not updated upon using SaveOrUpdate. Any idea what the issue is? (My parent key is defined and the children are generated). I add the children to the parent object's collection before persisting with saveOrUpdate. I'm using MySQL with hibernate 3 and my auto property is set to create-drop.
The test class:
public class Tester {
public static void main(String[] args) {
VideoChannel testChannel = new VideoChannel("Test Channel");
VideoChannelMap v = new VideoChannelMap(testChannel, "Test Map");
VideoSource sc2Vid = new VideoSource("starcraft-ii-ghost-of-the-past.mp4", "EinghersStreamingBucket");
testChannel.add(sc2Vid);
Session s = HibernateSessionFactory.getSession();
s.beginTransaction();
s.saveOrUpdate(v);
s.close();
}
}
The entities:
#Entity
public class VideoChannelMap {
#Id
String name;
#OneToMany(cascade=CascadeType.ALL)
List<VideoChannel> channelMap;
public VideoChannelMap(VideoChannel initialVid, String name)
{
this.name = name;
channelMap = new ArrayList<VideoChannel>();
channelMap.add(initialVid);
initialVid.setParent(this);
}
}
#Entity
public class VideoChannel {
#Id #GeneratedValue
Long id;
...
}
You have to actually commit your transaction. The behavior when you close a session with a transaction still open isn't very well defined and will likely depend on how your database is set up underneath.
Transaction t = s.beginTransaction();
s.saveOrUpdate(v);
t.commit();
s.close();
Obviously you should also have some try-catch-finally action going on in there for "real" code ;)