I have to create a very simple Spring "market" app.
No front-end needed
The Market:
The system must operate as a simplified market where users can be buyers or sellers.
Users:
user entity attributes: id:1, username:"User1", account:0
//account just gets incremented with each entry in the database.
The users can buy and sell items.
Items:
item entity attributes: id:3, name:Item1, ownerId:1.
example for interacting with items endpoints:
create: {id:1 name:"Item1", ownerId:1};
getAllItems with ownerId = 1 (use single query)
[
{
"id":3,
"name":”Item1”,
"ownerId":1,
“ownerUsername”:"User1"
}
]
Example:
"User1" owns "Item1". He wants to sell it for $100. He creates an active contract. Other users can review all active contracts and choose to participate. "User2" has enough money in her account and buys "Item1". The contract is now closed. "User1" receives $100 in his account. "User2" is the new owner of "Item1".
Contracts:
contract entity attributes: id, sellerId, buyerId, itemId, price,status. (The seller is the owner of the item and can not be the buyer)
endpoints - CRUD. Example for interacting with contracts endpoints:
create: {itemId : 3, price : 100}. Expected behavior: find the owner of item with id 3 in the DB (ownerId = 1) persist the new active contract in the DB:
{
"sellerId":1,
"itemId":3,
"price":100,
"active":true
}
update price of active contract by id: {"itemId":3, "price":200}
getAllActive contracts (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"itemId":3,
"price":200,
"active":true
}
]
closing active contract by id {"itemId":3, "buyerId":2}.
Expected behavior: update the accounts of users with id 1 and id 2.
getAllClosed contracts by optional parameters: itemId, sellerId, buyerId (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"buyerId":2,
“buyerUsername”:"User2",
"itemId":3,
"price":100,
"active":false
}
]
So far, these are my Entities:
BaseEntity:
#MappedSuperclass
public abstract class BaseEntity {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Users:
#Entity
#Table(name = "users")
public class User extends BaseEntity{
private String username;
private Long account;
private Set<Item> items;
public User() {
}
#Column(name = "username", nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "account", nullable = false)
public Long getAccount() {
return account;
}
public void setAccount(Long account) {
this.account = account;
}
#OneToMany(mappedBy = "id")
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
Items:
#Entity
#Table(name = "items")
public class Item extends BaseEntity{
private String name;
private String ownerUsername;
private User user;
public Item() {
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//get the id of the item's owner
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getOwnerUsername() {
return user.getUsername();
}
public void setOwnerUsername(String ownerUsername) {
this.ownerUsername = ownerUsername;
}
}
So, what should I do from here on?
If you've already created persistence layers (using Spring Data JPA or another mapper), You need to develop service logic and create a presentation layer.
like this (just user domain)
UserService (service layer)
#Service
#RequiredArgsConstructor
public class UserService {
private final UserJpaRepository repository;
#Transactional
public Long createUser(String username) {
User user = new User();
user.setUsername(username);
// other logic ...
repository.save(user);
return user.getId();
}
#Transactional(readonly = true)
public User getUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> IllegalArgumentsException("Not Found Entity."))
}
}
UserAPIController (presentation layer)
#RestController
#RequiredArgsConstructor
public class UserAPIController {
private final UserService userService;
#PostMapping("/users")
public ResponseEntity<Long> createUser(#RequestBody CreateUserDTO dto) {
Long userId = userService.createUser(dto.getUsername());
return new ResponseEntity(userId, HttpStatus.CREATED);
}
#GetMapping("/users/{id}")
public ResponseEntity<User> getUser(#PathVariable Long id) {
User user = userService.getUser(id);
return new ResponseEntity(user, HttpStatus.OK);
}
}
Related
I have user and role models. Thats look like this:
User:
#Document("user")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
private String id;
private String name;
private String username;
private String password;
#DBRef
private Collection<Role> roles = new ArrayList<>();
Role:
#Document("role")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Role {
#Id
private String id;
private String name;
}
And I have besides others those three methods to save user, save role and add role to user, like this:
#Override
public User saveUser(User user) {
log.info("Saving new user {} to the database", user.getName());
return userRepository.save(user);
}
#Override
public Role saveRole(Role role) {
log.info("Saving new role {} to the database", role.getName());
return roleRepository.save(role);
}
#Override
public void addRoleToUser(String username, String roleName) {
log.info("Adding role {} to user {}", roleName, username);
User user = userRepository.findByUsername(username);
Role role = roleRepository.findByName(roleName);
user.getRoles().add(role);
}
Also I have a CommandLineRunner to insert those data
#Bean
CommandLineRunner run(UserService userService) {
return args -> {
userService.saveRole(new Role(null, "ROLE_USER"));
userService.saveRole(new Role(null, "ROLE_ADMIN"));
userService.saveUser(new User(null,"Stefan", "stefanadmin", "stefanadmin", new ArrayList<>()));
userService.saveUser(new User(null,"Marko", "markoadmin", "markoadmin", new ArrayList<>()));
userService.saveUser(new User(null,"Jovan", "jovanobicanuser", "jovanobicanuser", new ArrayList<>()));
userService.addRoleToUser("stefanadmin", "ROLE_ADMIN");
userService.addRoleToUser("stefanadmin", "ROLE_USER");
userService.addRoleToUser("markoadmin", "ROLE_ADMIN");
userService.addRoleToUser("jovanobicanuser", "ROLE_USER");
};
}
And everything works fine except array column roles, its empty.
Its like this in JSON when I try to return all users:
{"id":"6331bda42c1fa17e41079c99","name":"Jovan","username":"jovanobicanuser","password":"jovanobicanuser","roles":[]}]
I'm following some tutorial for this stuff, guy is using MySql while I'm using Mongo, so I think problem is somewhere how I'm trying to connect those two tables.
What can cause problem like this, what I'm missing?
My controller:
#PostMapping
public ResponseEntity<UserCreateResponse> createUser(#RequestBody #Valid UserCreateRequest userDto,
BindingResult result)
throws InvalidRequestException {
if (result.hasErrors()) {
throw new InvalidRequestException("Request parameter validation failed");
} else {
return ResponseEntity.ok(userService.createUser(userDto));
}
}
Service:
public UserCreateResponse createUser(UserCreateRequest userDto) {
return convertEntityToDto(userRepository.insert(convertDtoToEntity(userDto)));
}
private User convertDtoToEntity(UserCreateRequest userDto) {
return modelMapper.map(userDto, User.class);
}
private UserCreateResponse convertEntityToDto(User user) {
return modelMapper.map(user, UserCreateResponse.class);
}
And the model is :
#Getter
#Setter
#Document("User")
public class User {
#Id
private String id;
#Indexed(unique = true)
private String userName;
private String name;
private String surname;
private String job;
}
Repository is just a class extending MongoRepository.
When I try to insert 2 User with same userName via postman post request, it is adding 2 exactly same item to db even if I specified #Indexed(unique = true) to userName field. Why does this happen and how can I fix it on Java side without breaking indexing function on the field(I want to index userName field to find faster)
First of all I apologize, I am new using this technology and I really have many doubts.
I am trying to send a json object to my controller class, the problem is that with #RequestBody all the data arrives but the foreign keys arrive null. Example enter a new user with the id of a role that already exists in the BD, the user data arrives complete but the role ID arrives null
First I register the data of the role via POST and everything works perfectly with #RequestBody, but when I try to register a user with the role_id that is already saved in the database also using #RequestBody it is saved with the id_rol null
My user entity:
#Entity
public class Usuario implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String correo;
private String pass;
private boolean activo;
#OneToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="rol_id", nullable = false)
private Rol rol;
public Usuario(){}
public Usuario(String correo, String pass, boolean activo, Rol rol) {
this.correo = correo;
this.pass = pass;
this.activo = activo;
this.rol = rol;
}
/*getter y setter*/
My role entity:
#Entity
//#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Rol implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
private boolean activo;
#OneToMany(mappedBy = "rol")
private List<Usuario> usuarios;
public Rol(){}
public Rol(String nombre, boolean activo, List<Usuario> usuarios) {
this.nombre = nombre;
this.activo = activo;
this.usuarios = usuarios;
}
/*getter y setter*/
User Repository:
#Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Serializable> {
public abstract Usuario findByCorreo(String correo);
}
Role Repository:
public interface RolRepository extends JpaRepository<Rol, Serializable> {
}
User controller class:
#RestController
#RequestMapping("/v1")
public class UsuarioController {
#Autowired
UsuarioRepository usuarioRepository;
#PostMapping("/usuario/add")
public #ResponseBody ResponseEntity<String> crear(#Valid #RequestBody Usuario usuario) {
try{
usuarioRepository.save(usuario);
return new ResponseEntity<String>("Registro Exitoso..", HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<String>("Ha ocurrido un Error..", HttpStatus.BAD_REQUEST);
}
}
}
role controller class:
#RestController
#RequestMapping("/v1")
public class RolController {
#Autowired
RolRepository rolRepository;
#PostMapping("/rol/add")
public #ResponseBody ResponseEntity<String> crear(#Valid #RequestBody Rol rol) {
try{
rolRepository.save(rol);
return new ResponseEntity<String>("Registro Exitoso..", HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<String>("Ha ocurrido un Error..", HttpStatus.BAD_REQUEST);
}
}
}
This is what I send to my controller:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": "true",
"rol_id": "5"
}
And this is what I receive:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": true,
"rol_id": null
}
How should I receive this body with id_rol = 5?
I know that I am doing something wrong in #RequestBody, I appreciate any example you can provide as I have searched and I have not found that. Thank you..!
This is because you are sending a wrong JSON structure to your REST Controller.
In your Usuario class, the role_id is actually an object field with a name rol which represents Rol class.
So you need to pass Rol with id as an JSON object in your request:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": "true",
"rol": {
"id":"5"
}
}
when I create GET response, I have Stackoveflow error
Controller for answer
#Controller
public class TaskViewController {
#Autowired
private TaskService taskService;
#RequestMapping(value = "/task/view", method = RequestMethod.GET)
public #ResponseBody
AjaxResponseBody getTask(#RequestParam String text) {
int id;
AjaxResponseBody result = new AjaxResponseBody();
Task task;
System.out.println(text);
try {
id = Integer.parseInt(text);
}
catch (Exception e) {
result.setMsg("Invalid task number");
return result;
}
task = taskService.findById(id);
if (task == null){
result.setMsg("Task not found");
return result;
}
result.setTask(task);
return result;
}
}
He uses class AjaxResponseBody for answer
public class AjaxResponseBody {
private String msg;
private Task task;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Task getTask() {
return task;
}
public void setTask(Task task) {
this.task = task;
}
}
and when this controller works I catch
2017-11-24 10:47:10.514 WARN 1448 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: tracker.models.Project_$$_jvstd06_4["user"]->tracker.models.User_$$_jvstd06_5["watched_project"]->tracker.models.Project_$$_jvstd06_4["user"]->tracker.models.User_$$_jvstd06_5["watched_project"]->tracker.models.Project_$$_jvstd06_4["user"]->tracker.models.User_$$_jvstd06_5["watched_project"]->tracker.models.Project_$$_jvstd06_4["user"]->tracker.models.User_$$_jvstd06_5["watched_project"]->
How I understand this happens because model User and model Project has links to each other. Model User has an optional field "watched_project".
#Entity
#Table(name = "users")
public class User {
#ManyToOne(fetch = FetchType.LAZY)
private Project watched_project;
public Project getWatched_project() {
return watched_project;
}
public void setWatched_project(Project watched_project) {
this.watched_project = watched_project;
}
And model Project has field with not empry field "author":
#Entity
#Table(name = "projects")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int id;
#Column(nullable = false)
#NotEmpty(message = "*Please provide project name")
private String projectName;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
How I can abort itteration? Or any way?
JSON serialization tries to serialize object and you have circular reference. There are plenty questions in SO about it. If you are using Jackson then you can use annotation #JsonIgnore for Project object inside User or User object inside Project.
Also you can use #JsonManagedReference and #JsonBackReference like in this answer.
When adding multiple relationships between nodes simultaneously, only some of them are created. In the example below, the calls to makeUser(...) are only populating some of the relationships.
Main
#Transactional
void clearDatabase() {
session.execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r");
}
void createPeople() {
Person mark = new Person("Mark");
mark.password = "mark123";
people.save(mark);
Organisation noxRentals = new Organisation("Nox Rentals");
organisations.save(noxRentals);
makeUser(noxRentals, mark, "Administrator", Right.ADMINISTRATE);
makeUser(noxRentals, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, mark, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, charlotte, "Reservations", Right.LOGIN, Right.SEND_QUOTES);
}
#Transactional
void makeUser (Organisation organisation, Person person, String role, Right...rights) {
IsUser account = organisation.addUser(person, role);
account.addRights(rights);
organisations.save(organisation);
}
void run() {
clearDatabase();
createPeople();
}
Resulting in (notice Nox has no relationships):
Organisation.java
#NodeEntity
public class Organisation extends NitroBaseEntity {
#Relationship(type = "IsUser", direction = Relationship.INCOMING)
Set<IsUser> users = new HashSet<>();
public IsUser addUser(Person person, String role) {
IsUser user = new IsUser(person, this, role);
this.users.add(user);
return user;
}
}
Person.java
#NodeEntity
public class Person extends NitroBaseEntity {
#Property
String password;
#Relationship(type = "IsUser", direction = Relationship.OUTGOING)
Set<IsUser> users = new HashSet<>();
public Set<IsUser> getUserAccounts() {
return this.users;
}
}
IsUser.java
#RelationshipEntity
public class IsUser {
#GraphId
Long id;
#StartNode
public Person person;
#EndNode
public Organisation organisation;
#Property
public String role;
#Property
public Set<Right> rights = new HashSet<>();
public IsUser (Person person, Organisation organisation, String role) {
this.person = person;
this.organisation = organisation;
this.role = role;
}
}
Complete source code: https://bitbucket.org/sparkyspider/neo4j-sandbox-4/src/22eb3aba82e33dfe473ee15e26f9b4701c62fd8e/src/main/java/com/noxgroup/nitro/config/DatabaseInitializer.java?at=master
There are two things missing-
The type has to be specified on the #RelationshipEntity as well, like this #RelationshipEntity(type = "IsUser")
In Organisation.addUser(), add the IsUser to the Person too, something like person.users.add(user);. The entities have to be navigable from both ends.