I'm learning MongoDB and Mongo Spring, and I have somethings to wonder.
I have a UserInfo document, a user can have multiple roles (document Roles). I insert as a Set, work good. But for the time being, I can insert a role, which not exist in document Roles.
For the example below, I insert roles as ADMIN2, while Document Roles only having "ADMIN", "USER" and "MOD"
I have a document named User:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User implements UserDetails,Serializable {
#Id
private String id;
private final static String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+#[a-zA-Z0-9-]+(?:\\\\.[a-zA-Z0-9-]+)*)|(?>[_.#A-Za-z0-9-]+)$";
#NotNull
#Field
#Size(min = 1, max = 50)
private String username;
#NotNull
#Field
//#Pattern(regexp = LOGIN_REGEX)
private String password;
#JsonIgnore
private Set<Roles> roles;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
Set<Roles> role = this.roles;
for(Roles roleItem : roles) {
authorities.add(new SimpleGrantedAuthority(roleItem.getCode()));
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
the UserInfo extends User
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserInfo extends User implements Serializable {
public UserInfo(String id, #NonNull String username, #NonNull String password, Set<Roles> roles) {
super(id, username, password, roles);
}
#Field
#NotNull
private String firstName;
#NotNull
#Field
private String lastName;
#Transient
#Getter(AccessLevel.NONE)
private String fullname;
#Field
#NotNull
#Email
private String email;
#Field
private String description;
#Field
private String title;
#Field
private String phoneNumber;
#Field
private String lang;
public String getFullname() {
if(StringUtils.isBlank(lang))
if(lang.equals("VI"))
return lastName + " " + firstName;
else
return firstName + " " + lastName;
return null;
}
}
My idea is, a User can have a lot roles (for now, i have "ADMIN", "MOD" and "USER")
Here is document Roles
#Document("roles")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Roles implements Serializable{
#Id
private String id;
#Field
private String code;
#Field
private String name;
}
Here how the document UserInfo look like:
{
"_id": {
"$oid": "638b72f79c9b3a78b623ea50"
},
"firstName": "truong",
"lastName": "hoang ngoc nghia",
"email": "truonghnn23232",
"title": "Dev",
"lang": "VI",
"username": "truonghnn",
"password": "$2a$10$Jsn1wPMdr.TlS8yyjQ6mF.OBTdrxTuaaDQ8Xz30hSWsKFRuB2bfKW",
"roles": [
{
"code": "ADMIN"
}
],
"_class": "com.example.Auth_ToDo.Domain.UserInfo"
}
So, the issue is, when I input the roles to UserInfo document, I use JSON like this:
"roles" : [
{"code":"ADMIN2"}
]
Everythings is running fine. But as you can see, roles here having value of ADMIN2, which not exist in document Roles (Document Roles having ADMIN, USER and MOD as roles.code)
How can I implement my code, so when I insert into UserInfo document, it check if my role code exist in document Roles? If not, it will return an exception? (I want it to check by default)
I tried to use Embbed Document, as using #DocumentRef, but not working. The result is
=
"roles": [
null
],
"_class": "com.example.Auth_ToDo.Domain.UserInfo"
}
The role returned as null when i input code as "ADMIN"
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'm getting this error while storing an object into database using persistence API. For me, it looks alright. Couldn't find the issue anywhere in the model class.
Please let me know if you need any other info.
Thanks in advance.
Error:
SEVERE: Servlet.service() for servlet [Project] in context with path
[/Project] threw exception [Request processing failed; nested
exception is org.springframework.orm.jpa.JpaSystemException:
org.hibernate.exception.SQLGrammarException: ERROR: column
"first_name" of relation "users" does not exist Position: 36; nested
exception is javax.persistence.PersistenceException:
org.hibernate.exception.SQLGrammarException: ERROR: column
"first_name" of relation "users" does not exist Position: 36] with
root cause org.postgresql.util.PSQLException: ERROR: column
"first_name" of relation "users" does not exist Position: 36 at
org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2102)
at
org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1835)
......
User.java
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#Column(unique = true, nullable = false)
private String email;
#Column(nullable = false)
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column( name = "phone" )
private String phone;
#OneToMany(targetEntity=Address.class, mappedBy="user",cascade=CascadeType.ALL)
private List<Address> address;
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
#ElementCollection
#CollectionTable(name = "authorities",
joinColumns = #JoinColumn(name = "user_id" ) )
#Column( name = "role" )
private Set<String> roles;
#Transient
private String password2;
public User()
{
roles = new HashSet<String>();
enabled=true;
}
public boolean isAdmin()
{
return roles.contains( "ROLE_ADMIN" );
}
public boolean isUser()
{
return roles.contains( "ROLE_USER" );
}
public Long getId()
{
return id;
}
public void setId( Long id )
{
this.id = id;
}
public String getEmail()
{
return email;
}
public void setEmail( String email )
{
this.email = email;
}
public String getPassword()
{
return password;
}
public void setPassword( String password )
{
this.password = password;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName( String firstName )
{
this.firstName = firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName( String lastName )
{
this.lastName = lastName;
}
public String getPhone()
{
return phone;
}
public void setPhone( String phone )
{
this.phone = phone;
}
public Set<String> getRoles()
{
return roles;
}
public void setRoles( Set<String> roles )
{
this.roles = roles;
}
public String getPassword2()
{
return password2;
}
public void setPassword2( String password2 )
{
this.password2 = password2;
}
}
Database:
create table users (
id int8 not null,
email varchar(255) not null unique,
enabled boolean not null,
first_name varchar(255),
last_name varchar(255),
password varchar(255) not null,
phone varchar(255),
primary key (id)
);
Controller:
#RequestMapping(value = "user/registration.html", method = RequestMethod.POST)
public String registrationPOST(#ModelAttribute User user, ModelMap models, SessionStatus sessionStatus)
{
user.setEnabled(false);
user = userDao.saveUser(user);
sessionStatus.setComplete();
models.put("status","Check your email to confirm your account");
return "user/registration";
}
i have this Rest-DSL:
// this api creates new user
rest("/user")
.post()
.type(User.class).to("jpa://com.project.User")
This is my entities:
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user")
private List<User> users;
}
my problem is in my swagger in the Body value parameter example. It contains like this:
{
"id": "string",
"role": {
"id": "string",
"users": [
{
"id": "string",
"roles": [
{}
]
}
]
}
}
quite complicated, although i need only id and id_role parameters to create (POST) new user. I hope the body example shows like this:
{
"id": "string",
"id_role": "string"
}
I realized that my entities are not created properly. These was i learned:
Configure CascadeType in associated JPA entities
#Entity
public class User {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<User> users;
}
to make class not recursive, set #JsonIgnore
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
#JsonIgnore
// this attribute will not appear inside Role class
private List<User> users;
}
I have base class as
#XmlRootElement
public abstract class BaseDO {
#Id
protected ObjectId id;
/**
* We'll only provide getters for these attributes, setting is done in #PrePersist.
*/
protected Date creationDate;
protected Date lastChange;
.....and user class as:
#Entity(value = "user", noClassnameStored = true)
#XmlRootElement(name = "user")
#XmlSeeAlso({BaseDO.class})
public class AtsUser extends BaseDO {
public static enum UserStatus {
CREATED, ACTIVE, INACTIVE, DELETED, CLOSED
}
#Indexed(unique = false)
private String firstName;
#Indexed(unique = false)
private String lastName;
#Indexed(unique = false)
private String email;
private String password;
#Embedded
private List<UserRoleDO> roles = new ArrayList<UserRoleDO>();
// private String userId; //TODO add this later
private UserStatus status;
private String success;
.....
the REST API is as follows:
#Path("user/validate")
public class AtsUserValidationService {
private AtsUserDao dao;
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
#GET #Path("/query")
#Produces(MediaType.APPLICATION_JSON)
public AtsUser getUserByEmailn(#QueryParam("email") String email) {
System.out.println("in getUserByEmailn");
dao = new AtsUserMongodbDao();
System.out.println("firstName " + email);
AtsUser atsUser = dao.getAtsUsersByEmail(email) ;
return atsUser ;
}
....
The morphia Dao is as follows:
#Override
public AtsUser getAtsUsersByEmail(String email) {
AtsUser atsUser = null;
if ((email == null) || email.isEmpty() ) {
return null;
}
System.out.println("getAtsUsersByEmail:" + email);
Query<AtsUser> query = mongoDatastore.find(AtsUser.class);
query.field("email").equal(email);
atsUser = query.get();
return atsUser;
}
.....
When I debug, I see the id field and creationDate fields in the java code, but the JSON does not contain that. Here is what my JSON looks like.
{
"id": null,
"code": "admin",
"desc": "admin",
"email": "admin#aa.com",
"firstName": "admin",
"lastName": "admin",
"password": "admin",
"status": "CREATED"
}
Why my id is null and how can i get elements from base class to show up in the JSON ?
I believe you need to annotate the base class with #Entity as well. It worked for me (using morphia 0.109).
I'm trying to update a number of fields at the same time in my "User" document. However, I only want to update some of the fields and not replace the entire document and it's the latter that I cannot seem to avoid. The method I have for doing this looks like so:
public void mergeUser(User user) {
Update mergeUserUpdate = new Update();
mergeUserUpdate.set("firstName", user.getFirstName());
mergeUserUpdate.set("lastName", user.getLastName());
mergeUserUpdate.set("username", user.getUsername());
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), mergeUserUpdate, User.class);
}
My user object does contain other fields - a password field being one of them - but if this was set to a value before it is promptly replaced with an empty string or removed entirely. So in the database, this:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"password" : "d26b7f5c0ed888e46889dd1e3d217816d070510596f495e156e9efe4b035fec5a1fe1be643955359",
"username" : "john#gmail.com",
"alias" : "john"
}
gets replaced by this after I call the mergeUser method:
{
"_id" : ObjectId("4fc34563c3276c69248271d8"),
"_class" : "com.test.User",
"username" : "john#gmail.com",
"firstName" : "John",
"lastName" : "Doe",
"address" : {
"addressLine1" : ""
}
}
If I look at the Update object I see it contains the following:
{$set={firstName=John, lastName=Doe, username=john#gmail.com}}
This looks correct to me and from my understanding of the MongoDB $set function, this should only set the values that are specified. I was therefore expecting the password field to remain unchanged and the other fields added or altered accordingly.
As a general discussion point, I'm ultimately trying to achieve some kind of "merge" functionality whereby Spring will auto-magically check which fields are present in the supplied User object and only update the database with the values that are filled in, not all the fields. That should be theoretically possible I would have thought. Anyone know of a nice way to do this?
Here's my user object just in case:
/**
* Represents an application user.
*/
#Document(collection = "users")
public class User {
#Id
private String id;
#NotEmpty( groups={ChangePasswordValidationGroup.class} )
private String password;
#Indexed
#NotEmpty
#Email
private String username;
private String firstName;
private String lastName;
private Date dob;
private Gender gender;
private Address address;
public enum Gender {
MALE, FEMALE
}
// /////// GETTERS AND SETTERS ///////////
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
The code above does work just fine. I made a silly mistake whereby after updating correctly I proceed to save the user object again, which replaces it with a new document.
Having another solution to update an Entity without some fields using Spring MongoTemplate:
DBObject userDBObject = (DBObject) mongoTemplate.getConverter().convertToMongoType(user);
//remove unnecessary fields
userDBObject.removeField("_id");
userDBObject.removeField("password");
//Create setUpdate & query
Update setUpdate = Update.fromDBObject(new BasicDBObject("$set", userDBObject));
mongoTemplate.updateFirst(new Query(Criteria.where("_id").is(user.getId())), setUpdate , User.class);
//Or use native mongo
//mongoTemplate.getDb().getCollection("user").update(new BasicDBObject("_id",user.getId())
, new BasicDBObject("$set", userDBObject), false, false);
Because it uses auto converter so is is very helpful when your entity has many fields.