Столкнулся с довольно странной проблемой. Имеется entity User и объекты этого класса нужно связать друг с другом через many-to-many relationship. Соответственно, помимо основной таблицы "user" должны появиться еще 2 таблицы "customer_authors" "author_customers"
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
// ID пользователя
private Long userId;
// "customer_authors"
@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(name = "customer_authors",
joinColumns = @JoinColumn(name = "customer_id"),
inverseJoinColumns = @JoinColumn(name = "author_id"))
private List<User> authorsList = new ArrayList<>();
// "author_customers"
@ManyToMany(cascade=CascadeType.ALL)
@JoinTable(name = "author_customers",
joinColumns = @JoinColumn(name = "author_id"),
inverseJoinColumns = @JoinColumn(name = "customer_id"))
private List<User> customersList = new ArrayList<>();
// getters, setters and other data.
}
Нигде в моем коде не используется Arrays asList() / не создается список с фиксированным размером. Именно, это является причиной подобной ошибки согласно поиску в гугл и stackoverflow...
В моем случае, если в @Service UserService использовать подобный код:
user.setUserEmail(userEmail);
user.setUserPassword(passwordEncoder.encode(userPassword));
userRepository.save(user);
User referrer = userRepository.findUserByUserId(referrerId);
referrer.getAuthorsList().add(user);
userRepository.save(referrer);
то получаю соответствующую ошибку -
UnsupportedOperationException
// excerpt
java.lang.UnsupportedOperationException: null
at com.sun.proxy.$Proxy142.save(Unknown Source) ~[na:na]
at info.md7.textpool.services.UserService.addUser(UserService.java:252) ~[classes/:na]
at info.md7.textpool.controllers.UserController.userRegistration(UserController.java:54) ~[classes/:na]
Но при этом, если для теста в контроллере использовать что-то подобное:
User customer = (User) userService.findUserByEmail(currentUser.getUsername());
User author = userRepository.findByUserEmail("sukkivulmo@desoz.com");
customer.getAuthorsList().add(author);
userRepository.save(author);
то действительно, в таблицу добавляется нужная информация ID реферрера и ID пользователя.
В чем может быть заключаться ошибка? Может быть, кто-то сталкивался с этим и знает, как исправить? Заранее благодарю!
Полный сниппет метода из UserService:
public boolean addUser( //todo добавить тип работы
String userFullname,
String userEmail,
String userPassword,
String prefLangs,
String prefCats,
Double paymentCost,
String paymentMethod,
String paymentWallet,
Long referrerId,
User user
) throws MessagingException {
User userFromDbEmail = userRepository.findByUserEmail(userEmail);
User referrer = userRepository.findUserByUserId(referrerId);
if(userFromDbEmail != null) {
return false;
}
/*
* Проверяем наличие данных в полях prefLang, prefCats, paymentCost, paymentMethod, paymentWallet.
* И если они имеются в одном из полей, то регистрируем пользователя, как Автора.
* В противном случае, создаем нового Заказчика.
*
*/
if(
prefLangs != null && !prefLangs.isEmpty() ||
prefCats != null && !prefCats.isEmpty() ||
paymentCost != null ||
paymentMethod != null && !paymentMethod.isEmpty() ||
paymentWallet != null && !paymentWallet.isEmpty()
) {
user.setUserFullname(userFullname);
user.setUserEmail(userEmail);
user.setUserPassword(passwordEncoder.encode(userPassword));
user.setRegDate(LocalDateTime.now());
user.setUserActive(false);
user.setRoles(Collections.singleton(Role.AUTHOR));
user.setActivationCode(UUID.randomUUID().toString());
user.setUserMode("author");
user.setReceiveEmails(true);
// Если пользователь был приглашен, то находим реферрера и добавлем в его список нового пользователя
User referrer = userRepository.findUserByUserId(referrerId);
user.setReferrerId(referrer);
userRepository.save(user);
/* Добавляем метаданные для юзера
С связи с особенностями верстки, данные скриптом вставляю в hidden input поле, после чего
получаю String разделенный запятыми и разобрав добавляю данные в user_meta
*/
assert prefLangs != null;
String[] prefLang = prefLangs.split(",");
for (String metaValue : prefLang) {
UserMeta userMeta = new UserMeta();
String metaKey = "prefLang";
userMeta.setMetaKey(metaKey);
userMeta.setMetaValue(metaValue);
userMeta.setUser(user);
userMetaRepository.save(userMeta);
}
assert prefCats != null;
String[] prefCat = prefCats.split(",");
for (String metaValue : prefCat) {
UserMeta userMeta = new UserMeta();
String metaKey = "prefCat";
userMeta.setMetaKey(metaKey);
userMeta.setMetaValue(metaValue);
userMeta.setUser(user);
userMetaRepository.save(userMeta);
}
UserMeta paymentMeta = new UserMeta();
paymentMeta.setMetaKey(paymentMethod);
paymentMeta.setMetaValue(paymentWallet);
paymentMeta.setUser(user);
userMetaRepository.save(paymentMeta);
if(paymentCost != null) {
UserMeta paymentCostMeta = new UserMeta();
paymentCostMeta.setMetaKey("paymentCost");
paymentCostMeta.setMetaValue(paymentCost.toString());
paymentCostMeta.setUser(user);
userMetaRepository.save(paymentCostMeta);
}
referrer.getAuthorsList().add(user);
userRepository.save(referrer);
} else {
user.setUserFullname(userFullname);
user.setUserEmail(userEmail);
user.setUserPassword(passwordEncoder.encode(userPassword));
user.setRegDate(LocalDateTime.now());
user.setUserActive(false);
user.setRoles(Collections.singleton(Role.CUSTOMER));
user.setActivationCode(UUID.randomUUID().toString());
user.setUserMode("customer");
user.setReceiveEmails(true);
// Если пользователь был приглашен, то находим реферрера и добавлем в его список нового пользователя
User referrer = userRepository.findUserByUserId(referrerId);
user.setReferrerId(referrer);
userRepository.save(user);
referrer.getCustomersList().add(user);
userRepository.save(referrer);
}
return true;
}
Откуда взялся UnsupportedOperationException?
Коллекции в Java могут иметь необязательные для реализации методы.
При вызове метода, имплементация которого не предусмотрена в данной реализации выбрасывается UnsupportedOperationException
:
public interface Iterator<E> {
//...
default void remove() {
throw new UnsupportedOperationException("remove");
}
//...
}
Наличие подобных методов в интерфейсе может вызвать недоумение, однако в данном случае, насколько я понимаю, это вызвано поддержкой обратной совместимости с более старыми версиями Java.
Как уже говорилось другими участниками, коллекции могут быть изменяемыми и неизменяемыми. Однако, вопреки общему мнению в данном случае исключение возникает не из-за использования Arrays.asList
(автор явно инициализирует поля новым объектом ArrayList'а
).
Хотя это было очень-очень близко и копать явно нужно было в эту сторону.
К сожалению, всё внимание было приковано к двум полям: authorsList
и customersList
.
Там были очень сомнительные зеркальные связи, да ещё и с каскадными операциями, которые в данном конкретном случае явно не могли быть применены в том виде. Более того при удалении cascade=CascadeType.ALL
ошибка пропадала. К ним мы вернемся позже.
Ошибка была куда прозаичнее:
user.setRoles(Collections.singleton(Role.AUTHOR));
Автор явно создаёт синглтон(немодифицируемую коллекцию) и передаёт его в список ролей.
После чего данный объект успешно сохраняется.
Ошибка же возникает после того как мы добавляем данного пользователя в список приглашенных клиентов другого пользователя и сохраняем второго пользователя(приглашающего), который был предварительно получен с помощью hibernate.
Сохранив второго пользователя и его связь с первым, hibernate смотрит на cascade=CascadeType.ALL
над customersList
и начинает обновлять объект первого пользователя, который лежал в данном списке. Перезаписывает все поля, встречающиеся в полях коллекции hibernate чистит после чего извлекает в них данные заново. И так он доходит до поля roles
с синглтоном, который лежит в первом пользователе, который в свою очередь лежит внутри списка второго пользователя. Пытается очистить его и получает UnsupportedOperationException
.
Чтобы исправить это достаточно просто написать:
user.getRoles().add(Role.AUTHOR);
И cascade=CascadeType.ALL
в данном случае не при чем.
Тем не менее его оставлять так нельзя. Потому что, при удалении приглашающего пользователя автоматически будут удалены и все авторы и клиенты, что как мне кажется не соответствует замыслам автора.
Над данными двумя полями(authorsList
и customersList
) стоит как минимум поставить
@ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, targetEntity = User.class)
А если говорить честно, то данные поля являются избыточными и от них можно просто избавиться.
Ведь для каждого приглашенного пользователя мы устанавливаем referrer(пригласившего пользователя).
Соответственно мы можем либо добавить обратное свойство invitedUsers
и отфильтровать его по роли.
Либо просто выбрать пользователей по referrer'у и роли.
Вам нужно посмотреть в сторону Array list он возвращает list
, Arrays.asList
возвращает список фиксированного размера, в который нельзя добавлять элементы. Изменяемый список можно создать как-то так
List<Integer> list1 = new ArrayList<>(Arrays.asList(10,20,60,30,22,70,89));
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Разбираюсь с аннотациямиХочу указать аннотацию @Max
Как можно настроить Progress Bar, чтобы было примерно как на скрине
Можно ли из 1 intent предать информацию в 2 разных Activity, по нажатию кнопки// Это о куда надо предать картинку