The problem is that by adding a "message" object type to the "StoreActivity" entity, the first time, it does so without any problem, when iterating a second time, the program throws the exception:Multiple representations of the same entity
I have tried in the parent entity with all types of "cascades" and it does not work
Entity StoreActivity :
#Entity
#Table(name="store_activities")
public class StoreActivity implements Serializable {
#EmbeddedId
StoreActivityPk storeActivityPk = new StoreActivityPk();
#ManyToOne
#JoinColumn(name = "store_id", insertable = false, updatable = false)
#MapsId("storeId")
private Store store;
#ManyToOne
#JoinColumn(name = "activity_id", insertable = false, updatable = false)
#MapsId("activityId")
private Activity activity;
#NotEmpty
#Column(length = 500, nullable=true)
private String comment;
#OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL,CascadeType.DETACH,CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.REMOVE})
#JoinColumns({
#JoinColumn(name="date_activity", referencedColumnName="dateActivity"),
#JoinColumn(name="store_id", referencedColumnName="store_id"),
#JoinColumn(name="activity_id", referencedColumnName="activity_id"),
#JoinColumn(name="store_activity_hour", referencedColumnName="storeActivityHour")
})
private List<Message> messages;
//Getters and setters ...
}
StoreActivityPk :
#Embeddable
public class StoreActivityPk implements Serializable{
private Long activityId;
private Long storeId;
#Temporal(TemporalType.DATE)
private Date dateActivity;
#Temporal(TemporalType.TIME)
private Date storeActivityHour;
}
Message entity :
#Entity
#Table(name = "messages")
public class Message implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#NotEmpty
#Column(length = 1000, nullable=false)
private String messageContent;
#Column(length = 1, nullable=false)
private Boolean messageRead;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="user_id")
private UserCheck userCheckSender;
#Temporal(TemporalType.TIMESTAMP)
private Date dateMessage;
//Getters and setters
}
Controller :
public class StoreActivityController {
#PostMapping("/save")
public String save(#Valid Message message, BindingResult result, Model model, RedirectAttributes flash,
SessionStatus sessionStatus, Authentication authentication,
#RequestParam(name = "storeId", required = true) Long storeId,
#RequestParam(name = "activityId", required = true) Long activityId,
#RequestParam(name = "dateActivity", required = true) #DateTimeFormat(pattern="yyyy-MM-dd") Date dateActivity) {
if (result.hasErrors()) {
model.addAttribute("title", "Mensajes");
model.addAttribute("subtitle", "Mensajes de la actividad diaria");
return "frontend/message/activitymessage";
}
UserCheck userCheck = (UserCheck) authentication.getPrincipal();
message.setUserCheckSender(userCheck);
StoreActivity storeActivity = storeActivityService.findById(activityId, storeId, new Date());
storeActivity.addMessage(message);
storeActivityService.saveStoreActivity(storeActivity);
sessionStatus.isComplete();
flash.addFlashAttribute("success", "Mensaje Enviado");
return "redirect:/message/activity/" + storeActivity.getStoreActivityPk().getActivityId() + "/store/"
+ storeActivity.getStoreActivityPk().getStoreId();
}
}
Actual result console :
2019-08-08 17:44:54.918 WARN 5024 --- [io-8090-exec-10] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [cl.tricotcorp.app.checklist.models.entity.UserCheck#1] are being merged. Detached: [cl.tricotcorp.app.checklist.models.entity.UserCheck#8f06f5d]; Managed: [cl.tricotcorp.app.checklist.models.entity.UserCheck#46781997]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [cl.tricotcorp.app.checklist.models.entity.UserCheck#1] are being merged. Detached: [cl.tricotcorp.app.checklist.models.entity.UserCheck#8f06f5d]; Managed: [cl.tricotcorp.app.checklist.models.entity.UserCheck#46781997]]
Related
Im learning, and so far i created many to many bidirectional database - user can create many groups and group can have many users - and i cannot find a way for my GroupsController Post mapping to work, from my understanding, it requires to get firstly Users id, in order to set the right relationship in Join table for Group, because the relationship should be set only when user create/join group, not when user create sign up procedure. Postman throws 500 and intelliJ:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null] with root cause
java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because the return value of "com.ilze.highlight.entity.Groups.getId()" is null
I use lombok - #Data, #Getter, therefore getId() should be available for use from Group class. My GroupsController with POST mapping when user decides to create a new group:
#RestController
#RequestMapping("api/groups") // pre-path
public class GroupsController{
#Autowired
private GroupsService groupsService;
#Autowired
private UserService userService;
#Autowired
private final GroupsRepository groupsRepository;
#Autowired
private UserRepository userRepository;
public GroupsController(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#GetMapping("/all-groups")
public List<Groups> getGroups(){
return (List<Groups>) groupsRepository.findAll();
}
#PostMapping("/user/{usersId}/create-group")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest){
Groups group = userRepository.findById(usersId).map(users -> {
long groupsId = groupRequest.getId();
// add and create new group
users.addGroup(groupRequest);
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(group, HttpStatus.CREATED);
}
}
Group database class:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Table(name = "group_collection")
public class Groups {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name ="group_name", unique = true, nullable = false, length = 20)
private String groupName;
#Column(name = "size", nullable = false)
private int size;
#Column(name = "strict", nullable = false)
private boolean strict;
#Column(name = "open", nullable = false)
private boolean open;
#Column(name ="description", length = 300)
private String description;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
},
mappedBy = "groups")
#JsonIgnore
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
And Users class for database:
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "username", unique = true, nullable = false, length = 100)
private String username;
#Column(name = "password", nullable = false)
private String password;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "create_time", nullable = false)
private LocalDateTime createTime;
#Enumerated(EnumType.STRING)
#Column(name = "role", nullable = false)
private Role role;
#Transient
private String accessToken;
#Transient
private String refreshToken;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
})
#JoinTable(name = "groups_x_user",
joinColumns = { #JoinColumn(name = "users_id") },
inverseJoinColumns = {#JoinColumn(name = "groups_id")})
private Set<Groups> groups = new HashSet<>();
public void addGroup(Groups group) {
this.groups.add(group);
group.getUsers().add(this);
}
public void removeGroup(long id){
Groups group = this.groups.stream().filter(g ->
g.getId() == id).findFirst().orElse(null);
if(group != null){
this.groups.remove(group);
group.getUsers().remove(this);
}
}
For reference my GroupsService implementation:
#Service
public class GroupsServiceImpl implements GroupsService{
private final GroupsRepository groupsRepository;
public GroupsServiceImpl(GroupsRepository groupsRepository) {
this.groupsRepository = groupsRepository;
}
#Override
public Groups saveGroup(Groups group) {
group.setCreateTime(LocalDateTime.now());
return groupsRepository.save(group);
}
#Override
public Optional<Groups> findByGroupName(String groupName) {
return groupsRepository.findByGroupName(groupName);
}
}
You need to persist the object from request. And since you have Many-2-Many relation, you can insert related object from both sides. In your case: just add existing user to the newly created group
The method will look something like that:
#PostMapping("/user/{usersId}/groups")
public ResponseEntity<Groups> createGroup(#PathVariable(value = "usersId") Long usersId, #RequestBody Groups groupRequest) {
Groups createdGroup = userRepository.findById(usersId)
.map(user -> {
groupRequest.setId(null); // ID for new entry will be generated by entity framework, prevent override from outside
groupRequest.getUsers().add(user); // add relation
return groupsRepository.save(groupRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found user with id = " + usersId));
return new ResponseEntity<>(createdGroup, HttpStatus.CREATED);
}
Problem
I am trying to store an object in my Postgres database. This consists of the Order.class, (List) OrderDevice.class, and a Department.class.
The important thing is that the OrderDevices are always stored new in the DB, but a Department may already exist.
When I try to save the object to my database using save I get the following error message: (shown below)
I get the error message "detached entity passed to persist: com.niclas.model.OrderDevice" if the department does not exist yet, if the department exists the error message looks like this: "detached entity passed to persist: com.niclas.model.Department".
Solution attempts
This solution cannot be used because I do not use bidirectional mapping.
(I don't want to use a bidirectional mapping because I want to access the departments without an order.)
I also tried to change the Cascade types to MERGE like in this solution
I also tried using #Transactional on the method
I also tried to save the children in the database first and then the parent like this:
departmentRepository.save(order.getDepartment()); orderDeviceRepository.saveAll(order.getDevices()); orderRepository.save(order);
I hope I have described my good enough and I am happy about suggestions for solutions
Error.log
The log can be viewed here. (The formatting did not work here)
Order.class
#Entity
#Table(name = "orders")
public class Order extends AuditModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "order_id")
private String orderId;
#Column(name = "department_id")
private long departmentId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "department", referencedColumnName = "id")
private Department department;
#JsonProperty("deviceList")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "order_id")
private List<OrderDevice> devices;
#JsonProperty("forename")
#Column(name = "sender_forename")
private String senderForename;
#JsonProperty("surname")
#Column(name = "sender_surname")
private String senderSurname;
#Column(name = "notes", columnDefinition = "TEXT")
private String notes;
#Column(name = "month")
private int month;
#Column(name = "year")
private int year;
public Order() {
}
... Getter/Setters
}
OrderDevice.class
#Entity
#Table(name = "order_devices")
public class OrderDevice extends AuditModel{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column( name = "order_id", insertable = false, updatable = false )
private String orderId;
#Column(name = "device_id")
private long deviceId;
#Column(name = "device_name")
private String deviceName;
#Column(name = "priceName")
private String priceName;
#Column(name = "price")
private double price;
#Column(name = "count")
private int count;
public OrderDevice() {
}
... Getters/Setters
}
Department.class
#Entity
#Table(name = "departments")
public class Department {
//TODO add Form Validation
//TODO better Naming for From Attributes on Frontend and Backend
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "department_name")
private String department;
#Column(name = "contact_person_forename")
private String forename;
#Column(name = "contact_person_surname")
private String surname;
#Column(name = "contact_person_mail")
private String mail;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private String houseNumber;
#Column(name = "location")
private String location;
#Column(name = "postal_code")
private int postalCode;
#Column(name = "country")
private String country;
#Column(name = "auto_send_invoice")
private boolean autoSend;
#Column(name = "registered")
private boolean registered;
public Department() {
}
... Getter/Setters
}
OrderController.class
#Slf4j
#RestController
public class OrderController {
private final DepartmentRepository departmentRepository;
private final OrderRepository orderRepository;
private final OrderDeviceRepository orderDeviceRepository;
public OrderController(OrderRepository orderRepository, DepartmentRepository departmentRepository,
OrderDeviceRepository orderDeviceRepository) {
this.orderRepository = orderRepository;
this.departmentRepository = departmentRepository;
this.orderDeviceRepository = orderDeviceRepository;
}
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody Order order) throws JsonProcessingException {
order.setOrderId(order.generateOrderId());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
order.getDevices().forEach(orderDevice -> {
orderDevice.setOrderId(order.getOrderId());
});
//departmentRepository.save(order.getDepartment());
//orderDeviceRepository.saveAll(order.getDevices());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
Update
If the objects are created in this way, no error will occur and the order will be successfully saved in the database.
However, I don't understand why it works this way and not via ObjectMapper. Does anyone know why?
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody JsonNode jsonNode) throws JsonProcessingException {
Order order = new Order();
JsonNode departmentJson = jsonNode.get("department");
Department department;
if ( departmentJson.get("id").canConvertToInt() ) {
department = departmentRepository.findDepartmentById(departmentJson.get("id").asInt());
} else {
department = new Department();
department.setDepartment(departmentJson.get("department").asText());
department.setForename(departmentJson.get("forename").asText());
department.setSurname(departmentJson.get("surname").asText());
department.setMail(departmentJson.get("mail").asText());
department.setStreet(departmentJson.get("street").asText());
department.setHouseNumber(departmentJson.get("houseNumber").asText());
department.setLocation(departmentJson.get("location").asText());
department.setPostalCode(departmentJson.get("postalCode").asInt());
department.setCountry(departmentJson.get("country").asText());
department.setAutoSend(departmentJson.get("autoSend").asBoolean());
department.setRegistered(departmentJson.get("registered").asBoolean());
}
order.setDepartment(department);
order.setOrderId(order.generateOrderId());
order.setDepartmentId(department.getId());
List<OrderDevice> orderDevices = new ArrayList<>();
JsonNode devices = jsonNode.get("deviceList");
for (JsonNode node : devices) {
//TODO replace this mess with objectMapper
if (node.has("count") && node.get("count").asInt() != 0){
OrderDevice device = new OrderDevice();
device.setOrderId(order.getOrderId());
device.setDeviceId(node.get("id").asLong());
device.setDeviceName(node.get("deviceName").asText());
device.setPriceName(node.get("priceName").asText());
device.setPrice(node.get("price").asDouble());
device.setCount(node.get("count").asInt());
orderDevices.add(device);
}
}
order.setDevices(orderDevices);
order.setSenderForename(jsonNode.get("forename").asText());
order.setSenderSurname(jsonNode.get("surname").asText());
order.setNotes(jsonNode.get("notes").asText());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
You can try to use instead of orderRepository.save(order) use orderRespostiory.saveOrUpdate(order).
Working with dropwizard and hibernate
Exception when I try to do a persist is
No validator could be found for constraint
'javax.validation.constraints.Size' validating type
'enums.ServiceType'. Check configuration for 'type'
Code is below:
#Table(name = "transactions",
indexes = {
#Index(name = "references_index", columnList = "reference_id")
}
)
public class Transaction {
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#NonNull
#NotEmpty
#Column(name = "reference_id", unique = true)
private String referenceId;
#NonNull
#NotEmpty
#Column(name = "state")
private String state;
#NonNull
#Column(name = "type")
#Enumerated(EnumType.STRING)
private ServiceType type;
#NonNull
#NotEmpty
#Column(name = "provider")
private String provider;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date", insertable = false, updatable = false)
private Date createdTimeStamp;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_updated", insertable = false, updatable = true)
private Date updatedTimeStamp;
public Transaction(String referenceId, String state, ServiceType type, String provider) {
this.referenceId = referenceId;
this.state = state;
this.type = type;
this.provider = provider;
}
}
where
public enum ServiceType {
TYPEA, TYPEB, TYPEC
}
doing a persist in the DAO
class TransactionDAO extends AbstractDAO<Transaction> {
/**
* Creates a new DAO with a given session provider.
*
* #param sessionFactory a session provider
*/
public TransactionDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
protected Transaction persistTransaction(Transaction transaction) {
return persist(transaction);
}
}
Adding a #Valid against my enum solves this. However I don't know why the error message said it was trying javax.validation.constraints.Size
code snippet from working code
#NonNull
#Valid
#Column(name = "type")
#Enumerated(EnumType.STRING)
private ServiceType type;
I have the following bean:
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
and this:
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long id;
#Column(name = "role")
private String role;
....
}
Inside controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
from client side I send following request:
terminalAdmin comes to the method looks like this
Why spring writes values into role field?
How to force spring write 250/251 into id field?
P.S.
I tried to write
InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(AdminRole.class, new PropertyEditorSupport() {
public void setAsText(String name) {
....
}
});
}
but setAsText method doesn't invoke.
This is not a good practice to populate model objects into to forms since Spring can bind fields to object even if they are not populated into the view if your init binder is not properly configured.
Easiest way is to create DTO objects, eg. you could create AdminTerminalDTO or AdminTerminalForm wich you populate to the view.
The Form could contain same fields as AdminTerminal excluding ID field or any other sensitive fields. You cant insert new ID's from the view since it can cause DB integrity errors.
After successful validation you just persist your model object filling it with DTO/Form Object.
Moreover your JSR-303 Annotations seem to be not used in a proper way.
The #Size Annotation is not proper a validation to check String length. You have to use #Length instead. You use #Size to check length of an arrays. #Size also works on Strings but #Length is more accurate.
You can't just send an Integer and just try to bind to your Set(spring does some weird binding as you can see now) . Instead you already done addNewAdmin method in your controller wich already informs that it adds an Admin User.
You have to assign admin role on the server side right in this method. First you can use DTO wich will contain eg. username,password and other fields. You annote them with proper JSR-303 Annotations. Using bindingResult you check if there were any validation errors. If form is validated fine, you just convert your DTO/Form object to Model object. Then you can add admin role and persist your model object.
I can write some example code if this tips are not enough.
EDIT:
public class TerminalAdminDTO {
private String username;
#Length(max = 255)
public String getUsername(){
return username;
}
public void setUsername(String username){
this.username = username;
}
public TerminalAdmin convertToTerminalAdmin(){
TerminalAdmin terminalAdmin = new TerminalAdmin();
terminalAdmin.setUsername(this.username);
return terminAdmin;
}
}
#Entity
#Table
public class TerminalAdmin {
#Id
#Column(name = "admin_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "email", nullable = false)
private String email;
#Column(name = "phone")
#Size(max = 255)
private String phone;
#Size(max = 255)
#Column(name = "name")
private String name;
#Column(name = "registration_date")
#Temporal(TemporalType.TIMESTAMP)
private Calendar createDate;
#Column(name = "password", nullable = false)
#Size(min=1, max = 255, message = "введите пароль длиной от 1 до 255 символов")
private String password;
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
#Column(name = "blocked")
private boolean blocked;
...
}
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#Valid TerminalAdminDTO terminalAdminDTO,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
if(result.hasErrors()){
return "errorPage";
}else{
userService.createAdminUser(terminalAdminDTO);
return "successPage";
}
}
#Service
#Transactional
public class UserServiceImpl implements UserService {
private final int ADMIN_ROLE_ID = 0;
#Autowired
EntityManager entityManager;
public void createAdminUser(TerminalAdminDTO terminalAdminDTO){
TerminalAdmin terminalAdmin = terminalAdminDTO.convertToTerminalAdmin();
AdminRole adminRole = entityManager.find(AdminRole.class,ADMIN_ROLE_ID);
terminalAdmin.getAdminRoles().add(adminRole);
entityManager.create(terminalAdmin);
}
}
I wrote it as an example of way doing it, this is not a ready-made code
I write my first java application to read rss stream and use spring, spring-data, hibernate.
My models.
RssFeed:
#Entity(name = "RssFeed")
#Table(name = "FEED")
#JsonIgnoreProperties({"rssChannel"})
public class RssFeed {
#Id
#GeneratedValue
#Column
private Integer id;
#Column(unique = true)
#Index(name = "title_index")
private String title;
#Column
#URL
private String link;
#Column
private String description;
#Column
private String content;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date pubDate;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date updateDate;
#ManyToOne
#JoinColumn(name = "channelId")
private RssChannel rssChannel;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "feed_category",
joinColumns = {#JoinColumn(name = "feed_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "category_id", nullable = false, updatable = false)})
private Set<RssCategory> rssCategories = new LinkedHashSet<RssCategory>();
}
RssChannel:
#Entity(name = "RssChannel")
#Table(name = "Channel",
uniqueConstraints = #UniqueConstraint(columnNames = {"link"}))
#JsonIgnoreProperties({"feeds"})
public class RssChannel implements Serializable{
#Id
#GeneratedValue
#Column
private Integer id;
#Column
private String title;
#Column(unique = true)
#org.hibernate.validator.constraints.URL
private String link;
#Column
#org.hibernate.validator.constraints.URL
private String image;
#Column
private String description;
#OneToMany(mappedBy = "rssChannel", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RssFeed> feeds = new LinkedList<RssFeed>();
}
And RssCategory:
#Entity(name = "RssCategory")
#Table(name = "CATEGORY")
#JsonIgnoreProperties({"rssFeeds"})
public class RssCategory {
#Id
#GeneratedValue
#Column
private Integer id;
#Column(unique = true)
private String title;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "rssCategories")
public Set<RssFeed> rssFeeds = new LinkedHashSet<RssFeed>();
}
I use CrudRepository for manipulation with data. When save RssFeed without many to many it`s ok:
RssChannel channel = rssChannelService.get(url.toString());
rssFeed.setRssChannel(channel);
rssFeedService.save(rssFeed);
But when i add RssCategory:
rssCategory rssCategory = rssCategoryService.findOrCreate("test");
rssFeed.getRssCategories().add(rssCategory);
rssFeedService.save(rssFeed);
get exception: rg.hibernate.PersistentObjectException: detached entity passed to persist: RssCategory.
My RssFeedServiceImpl:
#Service
public class RssFeedServiceImpl implements RssFeedService {
#Autowired
private RssChannelDAO rssChannelDAO;
#Autowired
private RssFeedDAO rssFeedDAO;
#Override
public Page<RssFeed> findAll(Pageable pageable) {
return rssFeedDAO.findAll(pageable);
}
#Override
public Page<RssFeed> findAll(int rssChannelId, Pageable pageable) {
RssChannel rssChannel = rssChannelDAO.findOne(rssChannelId);
return rssFeedDAO.findByRssChannel(rssChannel, pageable);
}
#Override
public RssFeed get(String title) {
return rssFeedDAO.findByTitle(title);
}
#Override
public RssFeed save(RssFeed rssFeed) {
return rssFeedDAO.save(rssFeed);
}
}
And RssCategoryServiceImpl:
#Service
public class RssCategoryServiceImpl implements RssCategoryService {
#Autowired
RssCategoryDAO rssCategoryDAO;
#Override
public RssCategory findOrCreate(String title) {
RssCategory category = rssCategoryDAO.findByTitle(title);
if (category == null) {
category = new RssCategory();
category.setTitle(title);
category = rssCategoryDAO.save(category);
}
return category;
}
}
How save many to many?
You probably need to save your RssCategory first, in order to have an ID to store in feed_category table. This last save will be automatically made when you make the assignment:
rssFeed.getRssCategories().add(rssCategory);
but first you need to do:
rssFeedService.save(rssCategory);
Probably you'll need to put this operations within a transaction.