I have these Objects:
#Data
#Entity
#Table
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -55089179131569489L;
private String username;
private String email;
private boolean admin;
private String name;
private String surname;
#OneToMany(mappedBy = "owner")
private List<Ad> ads;
}
and
#Entity
#Table
#Data
#EqualsAndHashCode(callSuper = true)
public class Ad extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4590938091334150254L;
private String name;
private String description;
private double price;
#Enumerated(EnumType.STRING)
private Category category;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "OWNER_ID")
private User owner;
}
When I try to execute a POST with an object of type Ad.class with inside an existing object of type User.class (already in the Database) the service saves only the Ad object and the join column "OWNER_ID" remains empty.
I think that the mapping is correct. Could you help me to figure out the problem?
This is my Repository:
#Repository
#Transactional(readOnly = true)
public interface AdRepository extends PagingAndSortingRepository<Ad, String>
{}
and this is my RestRepository
#RepositoryRestResource(collectionResourceRel = "ad", path = "ad")
public interface AdRestRepository extends PagingAndSortingRepository<Ad, String> {}
If I step back a little and generalize your problem,
You are trying to POST a sub resource and expect both actions of
making a new resource (Ad)
making association with the owner (User)
to be happened with a single call.
But unfortunately spring-data-rest does not support such a behavior. You need 2 calls to do this.
One to make the resource (Ad) => POST to /ads with actual payload
Second to make the association => POST to users/{ownerId} with the hateoas link of the resource created by the first call.
Take a look at this section of official documentation.
Related
I have the following #Entity:
#Entity
public class Person {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Date birthDate;
private String status;
private String city;
...
// many more attributes
}
I'm using the spring data rest as follow:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepositorio extends PagingAndSortingRepository<Person, Long>{
}
When I send the a post to the /api/person/ with a JSON containing all attributes of Person, only status is not set. Can someone help me?
Well, I think you're missing the get and set for status. Did you check it?
I have a problem using JPA and RelationsShips One to Many with Jackson and Spring Rest ... I try to find multiples solutions but anything is working for me , and I don't kno where is the problem.
For example I have a table Team that has One to Many/Many To One relationship
I have two repository one for Team and another for Player
Team >>> has Many >> Player
Player >>> many to one >> Team
My entity Team has the following content
#Entity
#Table(name = "teams")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Team {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long teamId;
private String abbreviation;
private String team;
private String simpleName;
private String logo;
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "team")
#Column(nullable = false)
private List<Player> players;
Theirs getters/setter , hashcodes and string similars.
On the other hand the entity Player
#Entity
#Table(name = "player")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
private String name;
So , I have the typical get call in a controller in a repository.
#RestController
#RequestMapping("/api/public/team")
public class TeamController {
#Autowired
private TeamRepository teamRepository;
#Autowired
private GenericMethods genericMethods;
#GetMapping(value = "/{id}")
public Team getPersona(#PathVariable("id") int id) {
return teamRepository.findOne(genericMethods.toLong(id));
}
And repository
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
Now , when I call this endpoint I receive the following answer and I think that is incorrect , I only need a List With Players
{
"id":2,
"teamId":0,
"abbreviation":null,
"team":null,
"simpleName":"Betis",
"logo":null,
"players":[
{
"id":1,
"team":2,
"category":{
"id":1,
"nombre":"juvenil a",
"language":null,
"description":null,
"league":[
],
"players":[
1,
{
"id":2,
"team":2,
"category":1,
"name":"hulio"
}
]
},
"name":"pepe"
},
2
]
}
I need to acces at information with Player and Team so I can't use #JsonIgnoreProperties
Could anyone help to solve this problem ?
Depending on what you really want to achieve you may try different options. I'm not sure if you're using (or intending to use) spring-data-rest or not.
1. Dedicated repository
Spring data rest will embed the related entities if they don't have their own repository. Try creating public interface PlayersRepository extends JpaRepository...
2. Lazy loading
Why are you using FetchType.EAGER ? Try without it.
3. Projections
Projections are only applicable to lists, not to individual entities (i.e. not explicitly what you're asking for). You can hide players from the Teams collection even if it was returned by default like so:
#Projection(name = "noPlayers", types = { Team.class })
public interface TeamWithoutPlayers {
Long getId();
long getTeamId();
String getAbbreviation();
String getTeam();
String getSimpleName();
String getLogo();
}
More info - Spring Data Rest Projections
4. Ignore during serialization in Team Entity using #JsonIgnore
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
Final thought
With spring-data-rest you can extend a CrudRepository instead of JpaRepository and access the item directly through the repository. That way you don't need to write a controller.
I'm using a JPA query that uses a specification to retrieve entities. When I execute the query, I'm getting the error:
org.springframework.data.mapping.PropertyReferenceException: No property name found for type Task!
I've looked at the answers to similar questions that have been asked on this site previously & tried to model my code to follow the patterns that were recommended but the code is still failing.
When I step through the code with a debugger, the expanded path in the criteria builder is returning the embedded ID class, but when the specification is actually used in the query it looks like the attribute is being applied to the base entity class.
Am I missing something obvious?
Here is the entity class:
#Entity
#Table(name = "TASKS")
public class Task implements Serializable {
#EmbeddedId
private TaskId id;
...more attributes, getters and setters
}
Here is the embedded ID entity class:
#Embeddable
public class TaskId implements Serializable {
#Column(name = "NAME", length = 100)
private String name;
...more attributes, getters and setters
}
Here is the specification builder that matches on the embedded id 'name' attribute:
public class HasTaskNameSpec {
private HasTaskNameSpec() {
}
public static Specification<Task> equals(String name) {
return (root, query, criteriaBuilder) -> {
return criteriaBuilder.equal(root.get("id").get("name"), taskName);
};
}
}
The query is executed on the repository as follows:
List<Task> results = taskRepository.findAll(HasTaskNameSpec.equals("foo"));
The repository itself is very simple:
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
List<Task> findByIdName(String name);
Page<Task> findByIdName(String name, Pageable page);
}
** EDIT added methods to repository as was suggested below **
Ahh, the root cause was totally in our codebase. There was a sort order being specified on the page that didn't include the embedded "id" attribute. The above code works.
'root.get({embeddedIdName}).get({subPropertyName})' is used to query on embeddedId using specification.
#Embeddable
public class ProjectId implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "PROJECT_NAME")
private String projectName;
#Column(name = "ORGANIZATION")
private String organization;
......
......
}
#Entity
#Table(name = "projects")
public class Project {
#EmbeddedId
private ProjectId projectId;
#Column(name = "STARTED_TIME")
private Timestamp startedTime;
#Column(name = "ACTIVE")
private String active;
#Column(name = "DESCRIPTION")
private String description;
......
......
}
In the above snippet, ProjectId is an embedded id. To query on projectName, we should use below snippet.
expression = root.get("projectId").get("projectName");
Demo application link.
Take a look at this link which has a similar query.
EmbbededId Lookup
The final answer suggests that you can add a method to your TaskRepository thus.
public interface TaskRepository extends JpaRepository<Task, TaskId>, JpaSpecificationExecutor<Task> {
public List<Task> findByIdName(String name);
}
I'm using spring-data-mongodb 1.8.2 (spring-boot-starter 1.3.1) and I have a fairly easy case at hand (in which I added fetch eager in desperation):
#Document(collection = "class_room")
public class ClassRoom implements Serializable {
#Id
private String id;
#NotNull
#Field("name")
private String name;
#ManyToOne**(fetch = FetchType.EAGER)**
#JoinColumn(name = "school_id")
private School school;
[...]
}
#Document(collection = "school")
public class School implements Serializable {
#Id
private String id;
#NotNull
#Field("name")
private String name;
#OneToMany(mappedBy = "school"**, fetch = FetchType.EAGER**)
private Set<Article> articles = new HashSet<>();
[...]
}
The repositories:
public interface SchoolRepository extends MongoRepository {
}
public interface ClassRoomRepository extends MongoRepository<ClassRoom,String> {
}
And the resources:
#RequestMapping(value = "/schools",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public List<School> getAllSchools() {
return schoolRepository.findAll();
}
#RequestMapping(value = "/classRooms",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public List<ClassRoom> getAllClassRooms() {
return classRoomRepository.findAll();
}
Now, can someone explain to me why the articles are correctly loaded when I perform the 'schoolRepository.findAll()'
but not when I perform the 'classRoomRepository.findAll()'?
And how can I make it happen?
TL;DR
A School has set of Articles
A classRoom has a School.
When I access a school directly: I see the set of Article
When access a school through a classRoom, the set of Article is empty.
Your approach to using object associations is a bit off. In Spring Data with Mongo the concepts of defining annotations to describe how associations happen is not the standard approach.
If you see the documentation here http://docs.spring.io/spring-data/data-mongo/docs/1.4.2.RELEASE/reference/html/mapping-chapter.html it helps to provide more clarity.
But to highlight, Mongo uses the concept of embedded objects and so ideally your data structure can be something like:
#Document(collection = "class_room")
public class ClassRoom implements Serializable {
#Id
private String id;
private String name;
private School school;
// Where School has the following fields and structure:
// private String id;
// private String name;
// private Set<Article> articles = new HashSet<>()
}
If you want school to be embedded in ClassRoom you leave it as above, else you can have School as a separate collection of its own. So:
#Document(collection = "school")
public class School implements Serializable {
#Id
private String id;
private String name;
private Set<Article> articles = new HashSet<>();
[...]
}
In the above School, it is a collection of its own and is not embedded in ClassRoom.
Typically, you have to just think differently from your traditional ORM approach when dealing with Mongo, or a NoSQL/Graph database.
I'm trying to fetch just a part of the model using Ebean in Play! Framework, but I'm having some problems and I didn't found any solutions.
I have these models:
User:
#Entity
#Table(name = "users")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends Model{
#Id
private int id;
#NotNull
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name")
private String lastName;
#NotNull
#Column(nullable = false)
private String username;
#NotNull
#Column(nullable = false)
private String email;
private String gender;
private String locale;
private Date birthday;
private String bio;
#NotNull
#Column(nullable = false)
private boolean active;
private String avatar;
#Column(name = "created_at",nullable = false)
private Date createdAt;
#OneToMany
private List<UserToken> userTokens;
// Getters and Setters omitted for brevity
}
UserToken:
#Entity
#Table(name = "user_tokens")
public class UserToken extends Model {
#Id
private int id;
#Column(name = "user_id")
private int userId;
private String token;
#Column(name = "created_at")
#CreatedTimestamp
private Date createdAt;
#ManyToOne
private User user;
// Getters and Setters omitted for brevity
}
And then, I have a controller UserController:
public class UserController extends Controller{
public static Result list(){
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
return Results.ok(Json.toJson(user));
}
}
I expected that, when using the .select(), it would filter the fields and load a partial object, but it loads it entirely.
In the logs, there is more problems that I don't know why its happening.
It is making 3 queries. First is the one that I want. And then it makes one to fetch the whole Model, and another one to find the UserTokens. I don't know why it is doing these last two queries and I wanted just the first one to be executed.
Solution Edit
After already accepted the fact that I would have to build the Json as suggested by #biesior , I found (out of nowhere) the solution!
public static Result list() throws JsonProcessingException {
User user = Ebean.find(User.class).select("firstName").where().idEq(1).findUnique();
JsonContext jc = Ebean.createJsonContext();
return Results.ok(jc.toJsonString(user));
}
I render only the wanted fields selected in .select() after using JsonContext.
That's simple, when you using select("...") it always gets just id field (cannot be avoided - it's required for mapping) + desired fields, but if later you are trying to access the field that wasn't available in first select("...") - Ebean repeats the query and maps whole object.
In other words, you are accessing somewhere the field that wasn't available in first query, analyze your controller and/or templates, find all fields and add it to your select (even if i.e. they're commented with common HTML comment in the view!)
In the last version of Play Framework (2.6) the proper way to do this is:
public Result list() {
JsonContext json = ebeanServer.json();
List<MyClass> orders= ebeanServer.find(MyClass.class).select("id,property1,property2").findList();
return ok(json.toJson(orders));
}