I am using postgresql database and I have the following tables: "user", "game" and "game_user" all inside the schema "allin". In my web application I'm using Hibernate 4.3.5, Spring MVC, and spring data jpa.
In my Game class I have the following OneToMany relationship:
#OneToMany(fetch=FetchType.EAGER)
#JoinTable(schema = "allin", name = "game_user",
joinColumns = { #JoinColumn(name = "game_id") },
inverseJoinColumns = { #JoinColumn(name = "user_id",nullable=true) })
private List<User> users;
And here is my unit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ServletInitializer.class, SecurityWebApplicationInitializer.class, WebMvcConfig.class})
#WebAppConfiguration
public class GameRepositoryTest
{
#Autowired
private GameService gameService;
#Autowired
UserService userService;
#Test
public void testRepostory()
{
List<User> users = new ArrayList<User>();
users.add(userService.findByUsername("luizcarlosfx"));
users.add(null);
users.add(null);
Game newGame = new Game(users, 10);
Game savedGame = gameService.save(newGame);
assertNotNull(savedGame);
Game getGame = gameService.findById(savedGame.getId());
assertEquals("There must be the same amount of users in the craeted game and in the saved game",newGame.getUsers().size(), getGame.getUsers().size());
}
}
When I run my unit test I get the following error: "There must be the same amount of users in the craeted game and in the saved game. expected:<3> but was:<1>."
The problem is that hibernate is not saving the null values that are inside the list of users, and I need to save my guest players as null, because they don't have any user. This part of the application must save games history of my multiplayer game.
My question is how to make hibernate save also the null values?
Using
inverseJoinColumns = { #JoinColumn(name = "user_id",nullable=true) })
would not allow you to save a Null-Object. You cannot save a Null (as object, but as value). Null is a not existing object. EVEN if you could manage to save a row filled with null-values - how would you assign this to ANY guest again?
Also this does not mean that the user_id can be null. Since foreign rows are referenced by id (as of your definition) allowing null for this would mean there could be only ONE entry for that.
Nullable = true in this case means: You can save a game, with NO upto MANY users. - But every of the MANY users needs to be different to null and have an id different to null.
Thus, if you call game.setUsers(null) - you can save. If you call game.getUsers().add(null) - you can't.
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
I'm having 3 JPA Entities like this as well as the corresponding JPA-Repositories.
#Entity
public class ChairEntity {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "chair_image")
private Set<ImageEntity> images = new HashSet<>();
...
}
#Entity
public class TableEntity {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "table_image")
private Set<ImageEntity> images = new HashSet<>();
...
}
#Entity
public class ImageEntity{
...
private String description;
#Lob
private byte[] data;
...
}
Using a REST-API these Objects are created and updated. This works usually fine, e.g. i may add multiple imageEntities at once like this (all codes blocks are inside their own transaction)
chairEntity.getImages().add(new ImageEntity(..));
chairEntity.getImages().add(new ImageEntity(..));
chairRepository.save(chairEntity);
...or update multiple ImageEntities of the same chairEntity at once.
chairEntity.getImages().stream().forEach(imageEntity -> {
imageEntity.setDescription("some other description");
}
chairRepository.save(chairEntity);
In both cases all Changes are successfully cascaded and saved.
If, however, I am updating an existing ImageEntity as well as adding another entity, it fails:
chairEntity.getImages().stream().forEach(imageEntity -> {
imageEntity.setDescription("some other description");
}
chairEntity.getImages().add(new ImageEntity(...));
chairRepository.save(chairEntity); // crashes
The exception is as followed (an equivalent error is thrown using h2db):
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "chair_image_pkey"
When inspecting the DB-Log, it seems like Hibernate is trying to:
inserting the new image (successfully)
updating the existing image (successfully)
inserting an entry into the Join-Table/Collection-Table (chair_image) referencing the chair and the existing image. This then throws this JdbcSQLIntegrityConstraintViolationException, as this combination of Foreign keys already exists (the old image already existed before).
Why is this happening and how do i solve it? Saving and Flushing the Changes individually inside the same transaction doesn't seem to work either.
A workaround, in case anyone else comes across this problem: Reverse the order of operations:
chairEntity.getImages().add(new ImageEntity(...));
chairRepository.saveAndFlush(chairEntity);
chairEntity.getImages().stream().forEach(imageEntity -> {
imageEntity.setDescription("some other description");
}
chairRepository.save(chairEntity); // crashes
The order in which hibernate executes the SQL-Statements stays the same, but due to the flush between, the faulty insert into the Join-Table no longer happens.
I'm implementing a Spring boot application and using Spring Data JPA in it. As you know you don't have to implement the repository interface for just CRUD methods, because Spring Data JPA creates an implementation on the fly. So I have just this:
public interface PersonRepository extends JpaRepository<Person, Long> {}
I'm working with one-to-many relationship, this is in my Person domain:
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY,
mappedBy = "person")
private Set<Contact> contacts = new HashSet<>();
I decided to write an integration test for child removal from the parent:
#Test
public void removeFromContacts() {
// given
Person person = new Person ("test person");
Contact contact = new Contact("test#gmail.com", "+123456789");
contact.setPerson(person);
person.getContacts().add(contact);
personRepository.save(person);
Person savedPerson = personRepository.findOne(person.getId());
Contact persistedContact = savedPerson.getContacts().stream().findFirst().orElse(null);
// when
savedPerson.getContacts().remove(persistedContact);
persistedContact.setPerson(null);
Person edited = personRepository.save(savedPerson);
// then
Assert.assertTrue(edited.getContacts().isEmpty());
}
This test fails. The reason is savedPerson.getContacts().remove(persistedContact) line doesn't change anything, remove method returns false. It's pretty strange, because I'm trying to remove an object from a hash set which has only one object with exact same hash code (equals() method returns true as well). According to this answer the contact object could've been altered somehow after adding it to the hash set. The only thing I can think of is it happened after this line: personRepository.save(person).
If I'm right then I'm really confused: how should I remove the contact from a person, and even if I find a way, is it okay for personRepository.save method to cause a set to malfunction? And if I'm wrong I would love to know the right answer.
Thanks in advance.
Class Compte and Class User joind to one-to-one relationship
public void delete(Integer integer){
User user = userRepository.findOne(integer);
Compte compte = user.getCompte();
compte.setUser(null);
compteRepository.save(compte);
user.setCompte(null);
userRepository.save(user);
compteRepository.delete(compte);
userRepository.delete(user);
}
First, here are my entities.
Player :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Player {
// other fields
#ManyToOne
#JoinColumn(name = "pla_fk_n_teamId")
private Team team;
// methods
}
Team :
#Entity
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class,
property="id")
public class Team {
// other fields
#OneToMany(mappedBy = "team")
private List<Player> members;
// methods
}
As many topics already stated, you can avoid the StackOverflowExeption in your WebService in many ways with Jackson.
That's cool and all but JPA still constructs an entity with infinite recursion to another entity before the serialization. This is just ugly ans the request takes much longer. Check this screenshot : IntelliJ debugger
Is there a way to fix it ? Knowing that I want different results depending on the endpoint. Examples :
endpoint /teams/{id} => Team={id..., members=[Player={id..., team=null}]}
endpoint /members/{id} => Player={id..., team={id..., members=null}}
Thank you!
EDIT : maybe the question isn't very clear giving the answers I get so I'll try to be more precise.
I know that it is possible to prevent the infinite recursion either with Jackson (#JSONIgnore, #JsonManagedReference/#JSONBackReference etc.) or by doing some mapping into DTO. The problem I still see is this : both of the above are post-query processing. The object that Spring JPA returns will still be (for example) a Team, containing a list of players, containing a team, containing a list of players, etc. etc.
I would like to know if there is a way to tell JPA or the repository (or anything) to not bind entities within entities over and over again?
Here is how I handle this problem in my projects.
I used the concept of data transfer objects, implemented in two version: a full object and a light object.
I define a object containing the referenced entities as List as Dto (data transfer object that only holds serializable values) and I define a object without the referenced entities as Info.
A Info object only hold information about the very entity itself and not about relations.
Now when I deliver a Dto object over a REST API, I simply put Info objects for the references.
Let's assume I deliever a PlayerDto over GET /players/1:
public class PlayerDto{
private String playerName;
private String playercountry;
private TeamInfo;
}
Whereas the TeamInfo object looks like
public class TeamInfo {
private String teamName;
private String teamColor;
}
compared to a TeamDto
public class TeamDto{
private String teamName;
private String teamColor;
private List<PlayerInfo> players;
}
This avoids an endless serialization and also makes a logical end for your rest resources as other wise you should be able to GET /player/1/team/player/1/team
Additionally, the concept clearly separates the data layer from the client layer (in this case the REST API), as you don't pass the actually entity object to the interface. For this, you convert the actual entity inside your service layer to a Dto or Info. I use http://modelmapper.org/ for this, as it's super easy (one short method call).
Also I fetch all referenced entities lazily. My service method which gets the entity and converts it to the Dto there for runs inside of a transaction scope, which is good practice anyway.
Lazy fetching
To tell JPA to fetch a entity lazily, simply modify your relationship annotation by defining the fetch type. The default value for this is fetch = FetchType.EAGER which in your situation is problematic. That is why you should change it to fetch = FetchType.LAZY
public class TeamEntity {
#OneToMany(mappedBy = "team",fetch = FetchType.LAZY)
private List<PlayerEntity> members;
}
Likewise the Player
public class PlayerEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pla_fk_n_teamId")
private TeamEntity team;
}
When calling your repository method from your service layer, it is important, that this is happening within a #Transactional scope, otherwise, you won't be able to get the lazily referenced entity. Which would look like this:
#Transactional(readOnly = true)
public TeamDto getTeamByName(String teamName){
TeamEntity entity= teamRepository.getTeamByName(teamName);
return modelMapper.map(entity,TeamDto.class);
}
In my case I realized I did not need a bidirectional (One To Many-Many To One) relationship.
This fixed my issue:
// Team Class:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<Player> members = new HashSet<Player>();
// Player Class - These three lines removed:
// #ManyToOne
// #JoinColumn(name = "pla_fk_n_teamId")
// private Team team;
Project Lombok might also produce this issue. Try adding #ToString and #EqualsAndHashCode if you are using Lombok.
#Data
#Entity
#EqualsAndHashCode(exclude = { "members"}) // This,
#ToString(exclude = { "members"}) // and this
public class Team implements Serializable {
// ...
This is a nice guide on infinite recursion annotations https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
You can use #JsonIgnoreProperties annotation to avoid infinite loop, like this:
#JsonIgnoreProperties("members")
private Team team;
or like this:
#JsonIgnoreProperties("team")
private List<Player> members;
or both.
I have 2 entity that have a relationship between them.
both of the entities has already created and inserted to the DB.
in some point the user has the ability to connect between them.
when im try doing it im getting:
javax.persistence.PersistenceException: Detected attempt to establish
WannaMeetUser("654321") as the parent of WannaMeetUser("123456") but
the entity identified by WannaMeetUser("123456") has already been
persisted without a parent. A parent cannot be established or changed
once an object has been persisted.
this is a transitive relation (user can have many friends from king of user):
the code is attached:
#Entity
public class WannaMeetUser {
#Id //signifies the primary key
#Column(name = "ID", nullable = false)
private Key id;
#ManyToMany
#Basic
private List<WannaMeetUser> userFriends = new ArrayList<WannaMeetUser>();
}
public void addFriendToWannaMeetUser(#Named("userId") String userId,
#Named("friendId") String friendId) {
EntityManager mgr = getEntityManager();
try
{
WannaMeetUser user = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", userId));
WannaMeetUser friend = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", friendId));
String coupleId = getcoupleId(userId.toString(), friendId.toString());
if (friend == null || user == null) {
throw new EntityNotFoundException("Object does not exist");
}
WannaMeetCouple couple=mgr.find(WannaMeetCouple.class, coupleId);
if (couple == null) {
couple = createCouple(userId.toString(), friendId.toString());
couple.setId(coupleId);
setUserJoined(couple, userId.toString(), friendId.toString(), true);
}
else {
if (isFriendAllready(couple, userId.toString(), friendId.toString()))
;
setUserJoined(couple, userId.toString(), friendId.toString(), false);
doAddFriend(user, friend, 10, 12321321);
mgr.persist(couple);
mgr.persist(friend);
mgr.persist(user);
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
mgr.close();
}
}
my question is What is the best way to crate such a relationship ?
Thanks
The messages says user 1 has already been persisted without having any parent, but now we try
to persist user 2 as a parent of user 1, but user 1 is known not to have a parent.
try to persist everything in one go using cascading instead. You can declare a cascading relation like this:
#ManyToMany(cascade=CascadeType.ALL)
private List<User> userFriends;
And then persist everything in one go using cascading:
User user1 = new User();
User user2 = new User();
user1.getUserFriends().add(user2);
// this persists the whole tree in one go
entityManager.persist(user1);
Parent-child entities should be used only for relationships that never change (e.g. a user and his photo). For dynamic relationships you have three options:
(A) Store references to other objects as a property (e.g. userFriends property in a User entity). If a relationship is unidirectional (e.g. User A likes User B), then you can update only one entity. For bidirectional relationships, you update both entities.
(B) Create a new entity type Relationship with two properties User A and User B. Create and delete these entities as necessary.
(C) When User A likes User B, create a new entity Relationship as a child entity of User A and use an id of User B as an id for this new Relationship entity.
The choice between these options depends on your data model and data access patterns.