#spring-boot #hibernate #spring-data-jpa #hibernate-mapping
#весенняя загрузка #гибернация #spring-data-jpa #отображение гибернации
Вопрос:
Примечание: я использовал Spring Data Jpa для сохранения.
Проблема:
У меня есть две модели: пользователь и значок
У меня есть список значков, принадлежащих пользователю в качестве элемента данных в классе User. У меня также есть пользователь в качестве члена данных в классе значков (т. Е. Создателя значка)
Я хочу установить связь между пользователем и элементом данных списка значков. отношения имеют тип OneToMany (т. Е. У одного пользователя будет много значков) и наоборот.
Я хочу, чтобы это работало таким образом, в коде ,
Когда я сохраняю объект badge с эмитентом (он же user), установленным для определенного объекта user, тогда не нужно добавлять его (значок) в список пользовательских значков, принадлежащих ему.
Я попытался создать связь, но она возвращает пустой список пользовательских значков в ответе REST API.
Модель значка
import javax.persistence.*;
@Entity
@Table(name = "badges")
public class Badge {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "badge_id")
private int mId;
@Column(name = "badge_name" , nullable = false , unique = true)
private String mName;
@Column(name = "badge_description")
private String mDescription;
@Lob
@Column(name = "badge_logo" , nullable = false)
private String mLogo;
@ManyToOne
@JoinColumn(name = "issuer_id")
private User mIssuer;
}
Пользовательская модель
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "users")
public class User {
@Id@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_id")
private long mId;
@Column(name = "username" , nullable = false , unique = true)
private String mUserName;
@Column(name = "fullname",nullable = false)
private String mFullName;
@Column(name = "salt")
private String mSa<
@OneToMany(mappedBy = "mIssuer",cascade = CascadeType.ALL)
private List<Badge> mOwnedBadges;
@OneToMany
@JoinColumn(name = "received_badges_id")
private List<Badge> mReceivedBadges;
}
CommandLineRunner
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class StartupExecutor implements CommandLineRunner {
@Autowired
private BadgeSerivce mBadgeSerivce;
@Autowired
private UserService mUserService;
@Override
public void run(String... args) throws Exception {
//TODO:: issuer cannot issue badge to itself
final User user1 = new User();
user1.setFullName("User1 FullName");
user1.setSalt("salt1");
user1.setUserName("User1 UserName");
mUserService.save(user1);
final User user2 = new User();
user2.setFullName("User2 FullName");
user2.setSalt("salt2");
user2.setUserName("User2 UserName");
mUserService.save(user2);
Badge badge1 = new Badge();
badge1.setDescription("Desc1");
badge1.setLogo("Logo1");
badge1.setName("Badge1");
badge1.setIssuer(user1);
mBadgeSerivce.save(badge1);
Badge badge2 = new Badge();
badge2.setDescription("Desc2");
badge2.setLogo("Logo2");
badge2.setName("Badge2");
badge2.setIssuer(user2);
mBadgeSerivce.save(badge2);
Badge badge3 = new Badge();
badge3.setDescription("Desc3");
badge3.setLogo("Logo3");
badge3.setName("Badge3");
badge3.setIssuer(user1);
mBadgeSerivce.save(badge3);
user1.setReceivedBadges(Arrays.asList(badge2));
user2.setReceivedBadges(Arrays.asList(badge1,badge3));
}
}
Note: It doesn’t save user Received Badges list also , if you can figure that out too , then I will really be thankful to you.
BadgeRepo
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BadgeRepo extends PagingAndSortingRepository<Badge,Long> {
}
UserRepo
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepo extends JpaRepository<User,Long> {
}
BadgeServiceImpl
package com.badging.spinnerbadger.SpinnerBadger.Services.Implentations;
import com.badging.spinnerbadger.SpinnerBadger.Repository.BadgeRepo;
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class BadgeServiceImpl implements BadgeSerivce {
@Autowired
private BadgeRepo mBadgeRepo;
@Override
public List<Badge> getAllBadges(int pageNumber , int sizeOfPage) {
if (sizeOfPage > 20) {
sizeOfPage = 20;
}
final Page<Badge> allPages = mBadgeRepo.findAll(PageRequest.of(pageNumber,
sizeOfPage));
if (allPages.getTotalElements() > 0) {
return allPages.toList();
} else{
return new ArrayList<Badge>();
}
}
@Override
public void save(Badge badge) {
mBadgeRepo.save(badge);
}
}
UserServiceImpl
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Repository.UserRepo;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepo mUserRepo;
@Override
public void save(User user) {
mUserRepo.save(user);
}
@Override
public List<Badge> getUsersReceivedBadgeList(long userId) {
final Optional<User> byId = mUserRepo.findById(userId);
return byId.orElse(new User()).getReceivedBadges();
}
@Override
public List<Badge> getUserOwnedBadgeList(long userId) {
final Optional<User> byId = mUserRepo.findById(userId);
return byId.orElse(new User()).getReceivedBadges();
}
}
Сгенерированный SQL с помощью Hibernate -> 1-й для модели пользователя и 2-й для модели значка
Hibernate: insert into users (fullname, salt, username, user_id) values (?, ?, ?, ?)
Hibernate: insert into badges (badge_description, issuer_id, badge_logo, badge_name, badge_id) values (?, ?, ?, ?, ?)
Комментарии:
1. после этого вы удаляете свои изменения
commit
?2. Я чувствую, что есть несколько вещей, которых не хватает, которые помогли бы ответить на этот вопрос. Во-первых, неясно, как реализованы BadgeService и UserServiced. В моем тестовом приложении я создал их интерфейсы, расширяющие JpaRepository. Во-вторых, я не понимаю, как отображается mReceivedBadges. Глядя на это, я чувствую, что должна быть другая таблица и объект. В моем примере приложения. Я не смог запустить эту часть как есть и оставил ее. С помощью обоих этих изменений я смог создать данные в этой функции, а затем получить пользователя с собственными значками в конечной точке REST.
3. @Vishrant Нет, я не сбрасываю и даже не фиксирую, я использую метод сохранения JpaRepository () для сохранения объекта.
4. @darrendanvers BadgeService и UserService отменяют операции BadgeRepo(inteface) и UserRepo(inteface) соответственно. Оба интерфейса репозитория расширяют JpaRepository. Я добавил код в сообщение, пожалуйста, обратитесь к нему.
5. @darrendanvers Что касается второго пункта, я также знаю, что есть некоторые проблемы с отношениями, я думаю, мне нужно будет добавить два отдельных объекта, один должен быть DisplayableBadge и RecievableBadge , например, в первом должен быть объект User, который управляет объектом, а во втором должен быть объект User, который являетсяПолучатель значка и должен быть владельцем (он же тот, кто его создал) пользовательского объекта. Я правильно думаю? , хотя мой первый приоритет — сначала получить список владельцев.
Ответ №1:
Я вижу пару вещей, которые здесь могут пойти не так.
-
Похоже, у вас не указаны транзакции. Добавьте
@Transactional
к тем компонентам или методам, которые должны участвовать в транзакции. По крайней мере, это должно включать все, что изменяет (в конечном итоге) базу данных, т. Е. Любой оператор, изменяющий управляемый объект, включая тот, который загружает его из базы данных иsave
операторов. Я ожидаю, что это будет фактической причиной проблемы, которую вы видите. -
Похоже, у вас нет кода, который синхронизирует обе стороны двунаправленных отношений. Итак, при вызове
badge1.setIssuer(user1)
user1
он не обновляется, поэтому, если вы вызоветеuser1.getOwnedBadges()
его, он все равно вернет неизмененное (пустое) значение.Я сомневаюсь, что в данном случае это проблема, но это приведет к тому, что отношения будут выглядеть по-разному в рамках одной транзакции, в зависимости от того, с какой стороны вы на это смотрите. И изменения на стороне, не являющейся владельцем (
User
в вашем случае), не будут сохранены. Так что это должно быть исправлено в любом случае. Смотрите также https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations / -
При сохранении объекта следует использовать экземпляр, возвращаемый
save
методом, а не тот, который передается вsave
качестве аргумента. Часто они одинаковы, но когда они не изменяют переданныйsave
, это может не привести к сохранению состояния в базе данных.
Если эти проблемы исправлены, а проблемы сохраняются, я рекомендую следующее, чтобы собрать больше информации о том, что происходит:
-
Активируйте протоколирование инструкций SQL, включая параметры, чтобы увидеть, что на самом деле сохраняется (и когда).
-
Создайте тест JUnit, тестирующий ваши сервисы. Это делает намного понятнее, что на самом деле выполняется, и позволяет создавать варианты для сравнения.
Комментарии:
1. Во-первых, я ценю время и энергию, которые вы тратите на решение моей проблемы. Теперь я сначала расскажу о третьем пункте: метод сохранения JpaRepository не возвращает сохраненный объект, поэтому это невозможно (хотя я могу создать пользовательский метод, который сохраняет объект и запрашивает его через некоторый уникальный идентификатор). Теперь я расскажу о 2-м вашем пункте, я не могу понять, о каком типе синхронизации кода вы говорите, я хорошо определил аннотацию jpa (видел несколько руководств, которые они сделали то же самое, и их сопоставление работает) и не нужно синхронизировать.
2.
save
возвращает экземпляр. Он унаследован отCrudRepository
docs.spring.io/spring-data/commons/docs/current/api/org /…3. Теперь я расскажу о вашем первом пункте, вы имеете в виду, что я должен добавить @Transactional для сохранения метода в классах реализации сервиса, верно? Я добавил SQL, сгенерированный hibernate для вставки объектов в базу данных, в конце моего сообщения см. Там.
4. Я обновил ответ, чтобы прояснить вопрос о синхронизации.
5.
@Transactional
должно быть добавлено кsave
методам в реализации сервиса, но также (и в данном случае более важно дляStartupExecutor.run