MongoRepository custom Id - spring

I have the following MongoDB Repository
public interface TeamRepository extends MongoRepository<Team, TeamId> {
....
}
And the following classes:
public abstract class DbId implements Serializable {
#Id
private final String id;
public DbId(final String id) { this.id = id;}
public String getId() { return id;}
}
public class TeamId extends DbId {
public TeamId(final String id) {
super(id)
}
}
As you can see, I have like a custom id for the repository (I have MongoRepository instead of something like MongoRepository). But, when I am trying to save a Team object, I get an error saying that MongoDB does not know how to generate DBId. Any clue?

MongoDb (or any database) would not know how to generate a string ID without you informing it what the value of the string is.
The default #Id is a string representation of ObjectId, which can be auto-generated by MongoDB. If you are changing the type of string ObjectId to a class, then at least the class needs to define:
** Conversion to string (serialisable), for example:
#Override
public String toString() {
return String.format(
"TeamID[uniqueString=%s]",
myUniqueString);
}
** How to generate the Id.
You can define a method in your TeamRepository i.e. save() to specify how your string can be generated. Alternatively you can check out
https://www.mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
Where the example specify getNextSequenceId() to generate NumberLong custom id. Hopefully that guides you to your answer.

Related

Spring JPA Treat NULL fields as undefined and ignore them

Let's say I have an object that's being passed from some UI to my graphQL
class Person{
String name;
String age;
String address;
}
This is a preexisting user, and I was sent the name and the address, but not the age. Can I tell JPA "Hey, if you see a NULL field, please do not overwrite it, just ignore it and update the other fields"
#GraphQLMutation(name = "createPerson")
public List<Person> createPeople(#GraphQLArgument(name = "people") List<Person> people) {
return personDao.saveAll(people);
}
#Repository
public interface StrategyDAO extends JpaRepository<Strategy, Integer>{
#PleaseDontOverWriteNulls
void saveAll(List<Person>)
}
The annotation in my repository is representative of what would be great to have but which I haven't been able to find.

QuerySyntaxException with enum

I have a UserAssignmentRole class like this :
#Data
#Entity
public class UserAssignmentRole {
...
#Enumerated(EnumType.STRING)
public Role role;
}
And the Role is enum, it looks like this:
public enum Role{
admin,
member,
pending
}
Now when in my repository I try to query to select all with role of admin, it gives me error:
#Query("select uar from UserAssignmentRole uar where uar.role=Role.admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
How this can be solved?
Error : org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'Role.admin'
Full error : https://pastebin.com/tk9r3wDg
It is a strange but intended behaviour of Hibernate since 5.2.x
An enum value is a constant and you're using a non-conventional naming (lowercase)
Take a look at this issue and Vlad Mihalcea's long explanation of the performance penalty.
If you’re using non-conventional Java constants, then you’ll have to set the hibernate.query.conventional_java_constants configuration property to false. This way, Hibernate will fall back to the previous behavior, treating any expression as a possible candidate for a Java constant.
You can try not to write this sql by yourself but with repository create code like this:
#Repository
public interface UserAssignmentRolelRepository extends JpaRepository<UserModel, Long>{
public List<UserAssignmentRole> findByRole(Role role);
}
And then:
#Autowired
UserAssignmentRolelRepository repository ;
public void someMethod(){
List<UserAssignmentRole> userAssignmentRoles = repository.findByRole(Role.admin);
}
UPDATE 1
As it was point out in this answer: non-conventional naming. You can change labels in your enum to uppercase.
public enum Role{
Admin,
Member,
Pending
}
and then:
#Query("select uar from UserAssignmentRole uar where uar.role=com.example.package.Role.Admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
UPDATE 2
But if you really want to have lowercase in DB.
It requires more code to change. Enum change to:
public enum Role{
Admin("admin"),
Member("member"),
Pending("pending");
private String name;
Role(String name) {
this.name = name;
}
public String getName() { return name; }
public static Role parse(String id) {
Role role = null; // Default
for (Role item : Role.values()) {
if (item.name.equals(id)) {
role = item;
break;
}
}
return role;
}
}
In UserAssignmentRole
// #Enumerated(EnumType.STRING)
#Convert(converter = RoleConverter.class)
private Role role;
And additional class:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class RoleConverter implements AttributeConverter<Role, String> {
#Override
public String convertToDatabaseColumn(Role role) {
return role.getName();
}
#Override
public Role convertToEntityAttribute(String dbData) {
return Role.parse(dbData);
}
}

Spring MongoRepository custom query

Hi I am new to using Spring with MongoRepository and I'm working on creating a custom query for MongoDB using Spring's MongoRepository.
What I would like to do is return a custom query for another variable in my model instead of the Object id.
for my model I have:
#Document(collection = "useraccount")
public class UserAccounts {
#Id
private String id;
private String accountNumber;
private String firstName;
private String lastName;
// getters and setters
}
inside of my repository I just extend the generic MongoRepository:
#Repository
public interface UserAccountsRepository extends MongoRepository<UserAccounts, String> {
}
I am trying to create a custom query that returns the accountNumber inside of my UserAccountsService:
#Service
public class UserAccountsService {
private final UserAccountsRepository userAccountsRepository;
public UserAccountsService(UserAccountsRepository userAccountsRepository) {
this.userAccountsRepository = userAccountsRepository;
}
// generic find by Object id
public UserAccounts findOne(String id) {
Optional<UserAccounts> userAccountsOptional =
userAccountsRepository.findById(id);
if(!userAccountsOptional.isPresent()) {
throw new RuntimeException("User Account Not Found");
}
return userAccountsOptional.get();
}
// would like to implement custom query to return UserAccount if
// found by accountNumber variable
public UserAccounts findOneByUserAccountNumber(String accountNumber) {
return dormantAccountsRepository.findOne(*need query here*);;
}
}
How would I go about creating a custom query to find a User Account by the accountNumber instead of the object id?
Any help would be great thanks!

How to use Java 8 Optional with Moxy and Jersey

Is it possible to use Jersey with Moxy to/from Json and Java 8 Optionals?
How to configure it?
You can declare following class:
public class OptionalAdapter<T> extends XmlAdapter<T, Optional<T>> {
#Override
public Optional<T> unmarshal(T value) throws Exception {
return Optional.ofNullable(value);
}
#Override
public T marshal(Optional<T> value) throws Exception {
return value.orElse(null);
}
}
And use like this:
#XmlRootElement
public class SampleRequest {
#XmlElement(type = Integer.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<Integer> id;
#XmlElement(type = String.class)
#XmlJavaTypeAdapter(value = OptionalAdapter.class)
private Optional<String> text;
/* ... */
}
Or declare in package-info.java and remove #XmlJavaTypeAdapter from POJOs:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = Optional.class, value = OptionalAdapter.class)
})
But here are some drawbacks:
Adapter above can only work with simple types like Integer, String, etc. that can be parsed by MOXY by default.
You have to specify #XmlElement(type = Integer.class) explicitly to tell the parser type are working with, otherwise null values would be passed to adapter's unmarshal method.
You miss the opportunity of using adapters for custom types, e.g. custom adapter for java.util.Date class based on some date format string. To overcome this you'll need to create adapter something like class OptionalDateAdapter<String> extends XmlAdapter<String, Optional<Date>>.
Also using Optional on field is not recommended, see this discussion for details.
Taking into account all the above, I would suggest just using Optional as return type for your POJOs:
#XmlRootElement
public class SampleRequest {
#XmlElement
private Integer id;
public Optional<Integer> getId() {
return Optional.ofNullable(id);
}
public void setId(Integer id) {
this.id = id;
}
}

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Resources