I've got a User class and an Email class.
I used the #OneToMany annotatioin to describe a realationship that one user can have many emails and one email is assigned to one user.
Now the problem is: when creating a user and an email and assigning the email to the user, I try to find a way that the Set of emails is getting initialized by JPA. Usually this works fine when I am doing em.find(User.class, "test");
This only does not work when I'm creating a user. The emails attribute is always of size 0 or even null. But when I create a user, then redeploy my application and then execute em.find(User.class, "test"), the emails attribute is set correctly of size 1 and I can access it.
#Entity
public class User implements Serializable {
private String username;
private String password;
private Set<Email> emails;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
public Set<Email> getEmails() {
return emails;
}
public void setEmails(Set<Email> emails) {
this.emails = emails;
}
#Id
#Column(name = "Username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Basic
#Column(name = "Password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
-
#Entity
public class Email implements Serializable {
private int id;
private String email;
private User user;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "Id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne
#JoinColumn(name = "user")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#Basic
#Column(name = "Email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
-
User user = new User();
user.setUsername("test");
user.setPassword("asdf");
em.persist(user);
Email e = new Email();
e.setEmail("test#test.com");
e.setUser(user);
em.persist(e);
return user;
after executing these statements, the user attribute emails is null (obviously). But strangely when I do em.find(User.class, "test"); in another method, the emails attribute is of size 0, even though the records are correctly inserted to the database.
When I now redeploy my application and call em.find(User.class, "test"); again, the emails attribute is of size 1.
I've already tried to call em.merge(); or em.refresh(); which did not work.
I am using glassfish 4.1.1. As transaction-type I use JTA and eclipselink for the JPA implementation
You need to add the email to the User as well and merge the user if you are doing it in two steps, or (you may need cascade=CascadeType.ALL) just put it in the User to begin with and persist the User if you are doing it in one step.
User user = new User();
Set<Email> emails = new HashSet<Email>();
user.setEmails(emails);
user.setUsername("test");
user.setPassword("asdf");
Email e = new Email();
e.setEmail("test#test.com");
e.setUser(user);
emails.add(e);
em.persist(user);
return user;
You need to set both sides of any bidirectional relationship, JPA will not do it for you. If you do not, the unset side will not reflect what is in the database until it is reloaded/refreshed from the database. You can force the refresh using em.refresh(user), but it is usually better to avoid the database hit and just add the email to the user's list of emails.
Related
I want to get a list of users by username, with enabled status and has phone number. I managed to get it working till I add the phone number parameter.
here is my code:
//crud repo
List<Users> findAllByUserNameContainsAndEnabledIsAndMobileNotNull(String userName, String enabled);
// controller
public Set<User> searchByName(#PathVariable String username) throws Exception {
Set<User> result = new HashSet<>();
result.addAll(userRepository.findAllByUserNameContainsAndEnabledIsAndMobileNotNull(username, "Y"));
return result;
}
// user class
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(precision = 18, scale = 0)
private BigDecimal id;
#Column(name = "Enabled", columnDefinition = "char(1) default 'Y'")
private String enabled;
private String username;
private String mobile;
// getters setters..
What is your phone number parameter called? You said it was working until that. The names have to match.
Here is all supported keywords in the method names:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
I have started new project and everything works as expected, so the problem is not in method name. Here is code I've tested:
Entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(precision = 18, scale = 0)
private BigDecimal id;
#Column(name = "Enabled", columnDefinition = "char(1) default 'Y'")
private String enabled;
private String userName;
private String mobile;
public User(String enabled, String userName, String mobile) {
this.enabled = enabled;
this.userName = userName;
this.mobile = mobile;
}
}
Repositiory
public interface UserRepository extends CrudRepository<User, BigDecimal> {
List<User> findAllByUserNameContainsAndEnabledIsAndMobileNotNull(String userName, String enabled);
}
Test
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
class UserRepositoryTest {
#Autowired
private UserRepository userRepository;
#Test
void test_findAllByUserNameContainsAndEnabledIsAndMobileNotNull() {
User user1 = new User("Y", "user1", "mobile");
User user2 = new User("Y", "user2", null);
userRepository.save(user1);
userRepository.save(user2);
List<User> all = userRepository.findAllByUserNameContainsAndEnabledIsAndMobileNotNull("user", "Y");
System.out.println("all = " + all);
}
}
And only one record in the output:
all = [User(id=1, enabled=Y, userName=user1, mobile=mobile)]
I have a OneToMany Relationship (User to EmailAddress)
Maybe I'm going about this the wrong way, My Database is empty but If I want to POST a User object and add it to the Database, along with the emailAdresses object and have the EmailAddress persisted also.
I want 2 records in the Database:
1 User and 1 EmailAddress (with a fk to User table)
Service Class
Currently what I've implemented to get this to work is this:
#Service
public class UserService {
private UserRepository userRepository;
private ModelMapper modelMapper;
public UserService(UserRepository userRepository, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.modelMapper = modelMapper;
//Used for mapping List
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
}
public User createUser(UserCreateDTO userCreateDTO) {
User user = modelMapper.map(userCreateDTO, User.class);
//persist User to EmailAddress object
if(user.getEmailAddresses() != null){
user.getEmailAddresses().forEach(user::persistUser);
}
return userRepository.save(user);
}
public UserDTO getUserById(Long id) {
User found = userRepository.findById(id).get();
return modelMapper.map(found, UserDTO.class);
}
// .....
Which I have seen used in some bidirectional relationships
User Entity
#Entity
#Table(name = "Users")
#Getter #Setter #ToString #NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private Long id;
private String firstName;
private String lastName;
private int age;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<EmailAddress> emailAddresses;
Email Address Entity
#Entity
#Table(name = "Email")
#Getter #Setter #ToString #NoArgsConstructor
public class EmailAddress {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="email_id", updatable = false, nullable = false)
private Long emailId;
private String email;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
Is there a better way to set up the Join relationship?
Sample POST Request
{"firstName":"Joe", "lastName":"Bloggs", "age": 30, "emailAddresses" : [ "joe-private#email.com" , "joe-work#email.com" ] }
I guess you need to associate this email with a user as well, not just set user to email entity.
public void persistUser(EmailAddress emailAddress) {
// set this email to the user
// if email EmailAddresses list is null you might need to init it first
this.getEmailAddresses().add(emailAddress);
emailAddress.setUser(this);
}
Firstly, I believe that a method persistUser should not be a part of a service layer - due to its implementation it mostly like a Domain layer method that should be implemented within a User entity class.
Secondly, since it's a POST method you shouldn't care of emails existence - you are adding a new user with a new set of emails
Closer to a question, I'd suggest you to try this:
public class UserService {
/************/
#Autowired
private UserManager userManager;
public void addUser(UserModel model) {
User user = new User(model);
List<EmailAddress> emails = model.getEmailAddresses().stream().map(EmailAddress::new).collect(Collectors.toList());
user.setEmailAddresses(emails);
userManager.saveUser(user);
}
}
and at the User add this:
public void setEmailAddresses(List<EmailAddress> emails) {
emails.forEach(item -> item.setUser(this));
this.emailAddresses = emails;
}
And don't forget to implement constructors for entities with model paremeters
I am using similar code to what is listed in: How to map one class with multiple tables in Hibernate/javax.persistance?
I was trying to write a sample login program, based on above example I map my user class to secondary table where I store password field. now when I retrieve back user entity. I also get secondary table field so password is also available in user object.
Is it possible, that during registration I want to use secondary table storage method but when I read back. it should not return password back with user?
How can I achieve this? I am looking for some JPA way like #transient ignore the particular column.
I wouldn't go for such implementation.
Best practice is to never store a clear-text password, but a digest instead:
#Entity
public class Account
{
#Column
private String username;
#Column(length = 32)
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = DigestUtils.md5Hex(password);
}
}
It's an uncommon requirement, and JPA patterns will do their best to fight against you :)
But... some way may still be possible:
using Entity Listeners:
#Entity
public class Account
{
#Column
private String username;
#Column
private String password;
#PostLoad
public void postLoad()
{
password = null;
}
}
be careful: when loaded inside a transaction, a null password may be eventually flushed on commit.
removing getter for password:
if you put annotations only on fields, you can remove getPassword() method. Even if the field is populated on load, it's not accessible by external java code.
using a #Transient combination:
#Entity
public class Account
{
#Column
private String username;
#Column
private String password;
#Transient
private String password2;
public String getPassword()
{
return password2;
}
public void setPassword(String password)
{
this.password = password;
this.password2 = password;
}
}
I have a class like this...
#Entity
public class User{
private String userId;
#Id
public String getUserId(){
return userId;
}
public void setUserId(String userId){
this.userId = userId;
}
}
#Embeddible
public class RegPk{
private String serial;
private String userId;
....
}
#Entity
#IdClass(RegPk.class)
public class Registration {
private String userId, serial;
private User user
#Id
#Column(name="SRL_C")
public String getSerial() {return serial;}
public void setSerial(String serial) {this.serial = serial;}
#ManyToOne(cascade={CascadeType.REFRESH})
#JoinColumn(name="USERID", referencedColumnName="USERID", nullable = false)
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
#Id
#Column(name="USERID", nullable = false)
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
RegPk pk = new RegPk();
pk.setSerial(dr.getSerial());
pk.setUserId(dr.getUserId());
Registration userOld = em.find(Registration.class, pk);
But when I try to run it I get null back. I swear I thought I had it working so...
1.) is this kind of thing even possible?
2.) what am I doing wrong?
Yes, it's possible, provided you use the MapsId annotation. Otherwise, you have two different fields mapped to the same column, which is invalid.
The javadoc provides an example which almost matches exactly with your situation.
I'm using apache open JPA to access my db at the server side. I'm trying to call from my gwt client to the server in order to retrive an entity object, but this requests always fails with the error message:NoClassDefFoundError org/apache/openjpa/enhance/PersistenceCapable.
I tried defining my enhanced entity classes at the gwt.xml file and it did not helped:
<set-configuration-property name="rpc.enhancedClasses" value="com.jobsin.domain.SystemUser,com.jobsin.domain.Keyword,com.jobsin.domain.Job">/
My server function:
public SystemUser getCurrentUser()
throws InvalidUser{
HttpServletRequest httpServletRequest = this.getThreadLocalRequest();
HttpSession session = httpServletRequest.getSession();
SystemUser user=null;
Cookie[] cookies = httpServletRequest.getCookies();
SocialNetworkCookie cookie = network.generateCookieObject();
if(!cookie.init(cookies)){
session.invalidate();
throw new InvalidUser(); // no user was connected, user need to login
}
//try get user details from db
EntityManager em = factory.createEntityManager();
SystemUserDAOImpl userdao = new SystemUserDAOImpl(em);
user = userdao.findUserByNetworkId(cookie.getMemberId());
//make user detach
em.close();
return user;
}
SystemUser Class :
#Entity
public class SystemUser implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -1673271845552139219L;
#Id
#GeneratedValue
private long id;
#NotNull
private String firstName;
#NotNull
private String lastName;
#NotNull
#Column(unique=true)
private String email;
#Temporal(TemporalType.TIMESTAMP)
private Date lastSearch;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Job> jobs = new HashSet<Job>();
#ManyToMany(cascade = CascadeType.ALL)
private Set<Keyword> keywords = new HashSet<Keyword>();
}
The easiest solution would be to add the OpenJPA binaries to the GWT client classpath.