Spring Data integration for ArangoDb gives back empty result list on queries - spring-boot

When I use queries from method names with ArangoDb and Spring Data integration I always get empty lists.
What is the problem in the way I use?
This is an example.
These are the Entities:
Authorization:
#Document("authorizations")
#Data // (Lombok)
public class Authorization {
#Id
private String id;
private String caption;
#Relations(edges = com.picktur.server.relations.UserAuthorized.class, lazy = true)
private User user;
}
User:
#Document("users")
#Data #NoArgsConstructor // (Lombok)
public class User {
#Id
private String id;
private String username;
#Relations(edges = com.picktur.server.relations.UserAuthorized.class, lazy = true)
private Collection<Authorization> authorizations;
}
Authorization Repository:
public interface AuthorizationRepo extends ArangoRepository<Authorization, String> {
Iterable<Authorization> findAllByUser(User user);
}
Edge between User and Authorization:
#Edge
#Data
public class UserAuthorized {
#Id
private String id;
#From
private User user;
#To
private Authorization authorization;
public UserAuthorized( User user, Authorization authorization) {
this.authorizedPhoto = user;
this.authorization = authorization;
}
}
Repository for UserAuthorized Egde:
public interface UserAuthorizedRepo extends ArangoRepository<UserAuthorized, String> {
}
Data Persistency logic:
User user = get_user_from_security_context();
Authorization authorization = new Authorization();
authorization.setUser(user);
...
authorization = authorizationRepo.save(authorization);
UserAuthorized userAuthorized = new UserAuthorized(user, authorization);
userAuthorizedRepo.save(userAuthorized);
Query that has empty result:
Iterable<Authorization> authorizations = authorizationRepo.findAllByUser(user);
All the documents and edges exist in DB but the result is empty.

Query derivation generates AQL query on document fields, eg. in case of findAllByUser it performs an AQL query like:
FOR a IN authorizations FILTER a.user == #user RETURN a
which obviously returns an empty array.
You can check the generated query enabling debug log level globally in your logback.xml.
According to your data model, to fetch all the authorizations of a given user you should perform something like this:
User user = userRepo.findById(userId).get();
Iterable<Authorization> authorizations = user.getAuthorizations();

Related

Child table is not mapping in OneToMany relationship in JPA

I am trying to establish One to many relationship between User and Role.
One user can have many role.
Here is the code for User class
#Entity
public class User {
#Id
private int id;
private String name;
private String password;
private String email;
private String phoneNo;
#OneToMany(
targetEntity = Role.class,
mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private Set<Role> roles;
// Getters, setters and Constructor
Code for the Role class
#Entity
public class Role {
#Id
#GeneratedValue
private int roleId;
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
// Getters, setters and Constructor
POST request on Postman is
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"role": [{
"role":"USER"
}]
}
Code on Configuration part
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http)throws Exception
{
http.csrf().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
Code On Controller part
#RestController
public class AdminController {
#Autowired
UserRepository userRepo;
#Autowired
BCryptPasswordEncoder encryptPassword;
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
userRepo.save(user);
return "User added Successfully";
}
}
Role table connection to database through Jpa
public interface RoleRepository extends JpaRepository<Role, Integer> {
}
User table connection to database through Jpa
public interface UserRepository extends JpaRepository<User,Integer>{
}
Here problem is User table is mapped properly but Role table is not getting mapped.
roleId role user_id
NULL NULL NULL
Where I am wrong ? Could anyone help me ?
On your controller method below, have you tried debugging the incoming request User object?
Anyways, I have below points here:
First, looking into your request body, the field for your roles is named role while your User object has a field roles thus, I'm pretty sure it is null during your processing since it will not be deserialized there due to mismatch field names. Try changing your request body to something like this:
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"roles": [{
"role":"USER"
}]
}
Second, if you check your database, the roles will be persisted however your foreign key user_id is null. This is expected. The cascade you did on the User object will only means that (since you use CascadeType.ALL) once you save the User object the save operation will also be cascaded to the Role object however, JPA still needs to know the relationship thus you have to set the user for each role object. Hence, you can update your controller method to something below:
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
// JPA needs to know this relationship...
user.getRoles().forEach(role -> role.setUser(user));
userRepo.save(user);
return "User added Successfully";
}
Now you can try and see that your expected behavior should now be happening.
Additional recommendations:
Why are we passing ID field on the user request? You can just remove that from your request body and use below to auto-generate your IDs to avoid Unique index or primary key violation exceptions on all of your entities:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
You can also remove the targetEntity = Role.class on the mapping as it is only used for generics and for your case clearly you are not using generics for Set. Update your User object for roles mapping:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Role> roles;
Lastly, it is better if you can wrap your incoming payload to a DTO since you would not want to expose your entity/model to your API but I am thinking this is just for your test environment.
You need to flush the changes to the database when using save(), try this instead:
userRepo.saveAndFlush(user);

Get LiveData from a many-to-many structure in android Room Architecture Component

Let's take a basic example
A table to store the users
#Entity (tableName="users")
class UsersEntity(
#PrimaryKey val id
var name:String,
...
)
A table to store the roles
#Entity (tableName="roles")
class RolesEntity(
#PrimaryKey val id
var name:String,
...
)
A table to store the many to many relation between users and roles
#Entity (tableName="roles")
class UserRoles(
#PrimaryKey val id
var userId:String,
var roleId:String
)
The pojo class I need in my View
class user(
var id:String,
var name:String,
.... other fields
var roles: List<Role>
)
In my ViewModel how can I pass a user result as LiveData having also the List<Role> filled?
Looking at a general way, I could:
have UserDao.getUserById(id) which returns LiveData from the users table and RoleDao.getRolesForUserId(id) which returns LiveData with a list of roles for a user. Then in my fragment, I can do viewModel.getUserById().observe{} and viewModel.getRolesForUserId().observe{}. But this basically means having 2 observers and I'm pretty confident that it's not the way to go.
probably other way would be to be able to mix them somehow in my repository or viewmodel so it returns what I need. I'll look into MediatorLiveData
Create a different model with the User and its Roles, and use #Embedded and #Relation annotations.
Take for example:
public class UserModel {
#Embedded
UserEntity user;
#Relation(parentColumn = "id", entityColumn = "userId", entity = UserRoles.class)
List<UserRoleModel> userRoles;
}
public class UserRoleModel {
#Embedded
UserRoles userRole;
#Relation(parentColumn = "roleId", entityColumn = "id")
List<RoleEntity> roles; // Only 1 item, but Room wants it to be a list.
}
You can use the UserModel from here.
It's ok to have multiple different data flow in one screen.
On the one hand we can talk about changing user roles list without changing user itself on the other hand user name can be changed without updating roles list. Addition benefit of using multiple data flow, you can show user data while loading user roles.
I suppose, you have pojo of user and roles to avoid synchronization issues. You can implement smooth data delivering (from db to view) like in sample below:
View model
public class UserRolesViewModel extends ViewModel {
private final MutableLiveData<Integer> mSelectedUser;
private final LiveData<UsersEntity> mUserData;
private final LiveData<List<RolesEntity>> mRolesData;
private DataBase mDatabase;
public UserRolesViewModel() {
mSelectedUser = new MutableLiveData<>();
// create data flow for user and roles synchronized by mSelectedUser
mUserData = Transformations.switchMap(mSelectedUser, mDatabase.getUserDao()::getUser);
mRolesData = Transformations.switchMap(mSelectedUser, mDatabase.getRolesDao()::getUserRoles);
}
public void setDatabase(DataBase dataBase) {
mDatabase = dataBase;
}
#MainThread
public void setSelectedUser(Integer userId) {
if (mDatabase == null)
throw new IllegalStateException("You need setup database before select user");
mSelectedUser.setValue(userId);
}
public LiveData<UsersEntity> getUserData() {
return mUserData;
}
public LiveData<List<RolesEntity>> getRolesData() {
return mRolesData;
}
}
It's better to encapsulate data source implementation in Repository class and inject it via DI like in this paragraph.
Database sample based on Many-to-Many paragraph from this article
Entities
Users
#Entity(tableName = "users")
public class UsersEntity {
#PrimaryKey
public int id;
public String name;
}
Roles
#Entity(tableName = "roles")
public class RolesEntity {
#PrimaryKey
public int id;
public String name;
}
User roles
This entity require special attention because we need to declare foreign keys to make joun operations futire
#Entity(tableName = "user_roles", primaryKeys = {"user_id", "role_id"}, foreignKeys = {
#ForeignKey(entity = UsersEntity.class, parentColumns = "id", childColumns = "user_id"),
#ForeignKey(entity = RolesEntity.class, parentColumns = "id", childColumns = "role_id")
})
public class UserRolesEntity {
#ColumnInfo(name = "user_id")
public int userId;
#ColumnInfo(name = "role_id")
public int roleId;
}
Dao
Users dao
#Dao
public interface UserDao {
#Query("SELECT * from users WHERE id = :userId")
LiveData<UsersEntity> getUser(int userId);
}
Roles dao
#Dao
public interface RolesDao {
#Query("SELECT * FROM roles INNER JOIN user_roles ON roles.id=user_roles.role_id WHERE user_roles.user_id = :userId")
LiveData<List<RolesEntity>> getUserRoles(int userId);
}
Data base
#Database(entities = {UsersEntity.class, RolesEntity.class, UserRolesEntity.class}, version = 1)
public abstract class DataBase extends RoomDatabase {
public abstract UserDao getUserDao();
public abstract RolesDao getRolesDao();
}

How to add One to One relationship with an Embeddable

Requirement:
To fetch the list of requests along with the requests feedback status.
What I'm doing now :
Fetch all the requests using JPQL query. Loop through each request and fetch status and set into response dto.
What I want to doing.
Fetch all the request along with the status using JPQl query
What I'm looking for :
How can I add one to one mapping for requests & status, so that I can fetch status.
Sample code
This is the MSREQUEST entity
#Entity
#Table(name = "MSREQUEST")
public class Request implements Serializable {
#Id
private long requestId;
#Column(name = "DESC")
private string desc;
//getter.. setter...tostring and hashcode
}
This is the status entity
#Entity
#Table(name="FEEDBACKSTATUS")
public class FeedbackStatus implements Serializable {
// composite-id key
#EmbeddedId
private RequestFeedBackId requestFeedbackKey = new RequestFeedBackId();
#Column(name="STATUS")
private Long status;
//getter.. setter...tostring and hashcode
}
This is the embeddable entity
#Embeddable
public class RequestFeedBackId implements Serializable {
#Column(name="REQUESTID")
private Long requestId;
#Column(name="FEEDBACKID")
private Long feedbackId;
}
Service
#Override
public List<MsaRequestSearchDto> searchMsaRequests(MsaRequestSearchDto msaRequestSearchDto)
throws MsaException, Exception {
List<MsaRequestSearchDto> msaRequestSearchDtoList = msaRequestRepoCustom.findMsaRequests(msaRequestSearchDto);
*// get feedback status loop thru and fetch status for each one. nee to avoid this
if(msaRequestSearchDtoList != null && msaRequestSearchDtoList.size() > 0){
// code to fetch dstatus
}*/
return msaRequestSearchDtoList;
}
JPQL query that Im using..
public String GET_MSA_REQUEST = "SELECT dsr FROM Request dsr WHERE 1=1";
E-R

Spring Boot + JPA + Lazy bi-directional collection testing

I'm new to JPA and Spring Data and I've faced problem with testing my application. I have two entities in my Spring boot application:
#Getter
#Setter
#Entity
public class User extends SomeBasicEntity {
#NotNull
#Column(unique = true)
private String login;
#NotNull
private String password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
#JsonManagedReference
private List<Role> roles;
}
#Getter
#Setter
#Entity
#Table(name = "ROLE")
public class Role extends SomeBasicEntity {
#NotNull
private String name;
#ManyToOne
#NotNull
#JsonBackReference
private User user;
}
I have implemented dedicated JPA repositories for them (as JpaRepository).
I have also implemented transactional facade for the management:
#Service
#Transactional
public class Facade {
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
public User addNewUser(User user) {
return userRepository.save(user);
}
public Role addNewRole(Role role, User user) {
role.setUser(user);
return roleRepository.save(role);
}
public User findUserByLogin(String login) {
User user = userRepository.findByLogin(login);
if (user != null) {
return user;
} else {
throw new FacadeRuntimeException("User " + login + " does not exists");
}
}
}
Finally, I've created RestController for using the facade (I've hardcoded login name value just for test purposes):
#RestController
#RequestMapping(value = "/users")
public class UserController {
#Autowired
private Facade facade;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public User getUserByLogin() {
User user = facade.findUserByLogin("jahu");
return user;
}
}
And now when I run the application, use facade methods to create user and role (code is irrelevant so I dont paste it here) and check REST response on localhost/users/login there is properly serialized User object WITH list of roles (with single role I've created actually).
Question 1: Why roles are present in Json object whereas I didn't explictly call getRoles on the user object? With lazy fetch type I believe collection should be retrived only when getter is called (maybe JSON serializer is calling the method?)
Nevertheless, I have second problem as I want to test my facade with jUnit (on h2 db), however in test method the roles on User object are always null (even if I explicltly call getter):
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = FacadeApplication.class)
#Transactional
public class FacadeTest {
private static final String USER_LOGIN = "jahu";
private static final String ROLE_NAME = "role name";
#Autowired
Facade sut;
#Before
public void setUp() throws Exception {
User user = new User();
user.setLogin(USER_LOGIN);
user.setPassword("jahu");
user = sut.addNewUser(user);
Role role = new Role();
role.setName(ROLE_NAME);
sut.addNewRole(role, user);
}
#Test
public void shouldFindUserRoles() {
User user = sut.findUserByLogin(USER_LOGIN);
assertThat(user).isNotNull();
assertThat(user.getLogin()).isEqualTo(USER_LOGIN);
List<Role> roles = user.getRoles(); // HERE I call the getter
assertThat(roles).isNotNull().isNotEmpty();
}
}
Question 2: Why can I not access roles in test context even though I am using the same method as in RestController (where roles are always obtained)? Without it I am not able to fully test my app and it concerns me very much.
Note: entity names are just for description of the problem, so please don't suggest that I should remodel my entities in order to assign Role to User.

Hibernate: ManyToMany inverse Delete

I have a ManyToMany relationship between two tables, User and Keyword. The User is the owner of the relationship. If I delete a User, I remove first all Keywords from this User and then delete the User. This works as expected.
But I don't know how to delete a Keyword and automatically delete the relations to all Users.
Here is my code so far.
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "id")
#GeneratedValue
private Integer id;
#Column(name = "name")
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(name = "user_has_keyword", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "keyword_id"))
private List keywords = new ArrayList();
// Getters and setters
...
}
#Entity
#Table(name = "keyword")
public class Keyword {
#Id
#Column(name = "id")
#GeneratedValue
private Integer id;
#Column(name = "keyword")
private String keyword;
#ManyToMany(mappedBy = "keywords")
private List users = new ArrayList();
// Getters and setters
...
}
#Service("myService")
#Transactional("transactionManager")
public class MyService {
#Resource(name = "sessionFactory")
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
public List getAllUsers() {
Session session = this.sessionFactory.getCurrentSession();
Query query = session.createQuery("FROM User");
return query.list();
}
public User getUser(Integer id) {
Session session = this.sessionFactory.getCurrentSession();
return (User) session.get(User.class, id);
}
public void addUser(User user) {
Session session = this.sessionFactory.getCurrentSession();
session.save(user);
}
public void deleteUser(User user) {
Session session = this.sessionFactory.getCurrentSession();
// 1st, delete relations
user.getKeywords().clear();
session.update(user);
// 2nd, delete User object
session.delete(user);
}
public Keyword getKeyword(Integer id) {
Session session = this.sessionFactory.getCurrentSession();
return (Keyword) session.get(Keyword.class, id);
}
public Keyword addKeyword(Keyword keyword) {
Session session = this.sessionFactory.getCurrentSession();
session.save(keyword);
return keyword;
}
public void deleteKeyword(Keyword keyword) {
Session session = this.sessionFactory.getCurrentSession();
// 1st, delete relations
keyword.getUsers().clear();
session.update(keyword);
// 2nd, delete User object
keyword = getKeyword(keyword.getId());
session.delete(keyword);
}
}
#Controller
public class MyController {
#Resource(name = "myService")
private MyService myService;
#RequestMapping(value = "/add", method = RequestMethod.GET)
public String add(Model model) {
Keyword k = new Keyword();
k.setKeyword("yellow");
k = myService.addKeyword(k);
User u1 = new User();
u1.setName("Bart");
u1.getKeywords().add(k);
myService.addUser(u1);
User u2 = new User();
u2.setName("Lisa");
u2.getKeywords().add(k);
myService.addUser(u2);
return "/";
}
#RequestMapping(value = "/delete/user", method = RequestMethod.GET)
public String deleteUser(Model model) {
User u = myService.getUser(1);
myService.deleteUser(u);
return "/";
}
#RequestMapping(value = "/delete/keyword", method = RequestMethod.GET)
public String deleteKeyword(Model model) {
Keyword k = myService.getKeyword(1);
myService.deleteKeyword(k);
return "/";
}
}
If I browse to /delete/keyword I get the following exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.blabla.prototype.Keyword.users, no session or session was closed
I've googled and tried many different things, but nothing works.
I appreciate any help.
Thank you very much,
Marco
The LazyInitializationException has nothing to do with deletion. You're loading a Keyword in your controller. This makes the service load the keyword, without initializing its lazy list of users. This keyword is then returned to the controller, and the transaction is committed, and the session is closed, making the keyword detached from the session.
Then you pass this detached keyword to the service to delete it. The service thus receives a detached keyword, and tries to access its list of users. Since the keyword is detached and the list of users hasn't been loaded yet, this causes a LazyInitializationException.
The service method should take the ID of the keyword to delete as argument, load it, and thus work with an attached keyword, and then proceed with the deletion.
Now to answer your question, you got it right for the user deletion: you remove all the keywords from the user to delete, because the user is the owner of the association. Apply the same logic when deleting a keyword : remove the keyword from all the users referencing it, and delete the keyword:
public void deleteKeyword(Integer id) {
Keyword keyword = getKeyword(id);
for (User user : keyword.getUsers()) {
user.removeKeyword(keyword);
}
session.delete(keyword);
}
Note that you don't have to call update() when working with attached entities. The state of attached entities is automatically and transparently saved to the database.

Resources