Spring&Hibernate problemy z ManyToMany :)

0

Cześć,
tworzę aplikację webową w Spring z użyciem Hibernate i napotkałem na błąd, który w sumie pojawił się znikąd i nie wiem dlaczego nie działa jak powinno, chociaż wcześniej było dobrze :D Ale do rzeczy, przy rejestracji użytkownika nie generuje nowego id dla encji asocjacyjnej (account_role) - tak mi się wydaje

A więc:

W bazie danych takie encje:
screenshot-20180329230218.png

Kod do zapisu użytkownika nowego w bazie

    @Transactional
    public RoleEntity getRoleByName(String name) {
        Session session = sessionFactory.getCurrentSession();
        Query query = session.createQuery("FROM RoleEntity where name =:name");
        query.setParameter("name", name);
        return (RoleEntity) query.uniqueResult();
    }

    @Transactional(rollbackOn = Exception.class)
    public void saveUser(AccountEntity user) {
        Session session = sessionFactory.getCurrentSession();
        user.setRoles(Collections.singletonList(getRoleByName("Pracownik")));
        session.save(user);
    }

Mapowanie encji Account:


import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.util.Collections;
import java.util.List;

@Entity
@Table(name = "account", schema = "apteka_magazyn")
public class AccountEntity {
    private int id;
    private String name;
    private String password;
    private boolean blocked;
    private String email;
    private List<RoleEntity> roles;


    public AccountEntity(String name, String password, RoleEntity role, String email, boolean blocked) {
        this.name = name;
        this.password = password;
        this.roles = Collections.singletonList(role);
        this.email = email;
        this.blocked = blocked;
    }

    public AccountEntity(String name, String password, List<RoleEntity> roles, String email, boolean blocked) {
        this.name = name;
        this.password = password;
        this.roles = roles;
        this.email = email;
        this.blocked = blocked;
    }

    public AccountEntity() {
    }

    @ManyToMany(cascade = { CascadeType.ALL })
    @Fetch(FetchMode.JOIN)
    @JoinTable(
            name = "account_role",
            joinColumns = { @JoinColumn(name = "user_id") },
            inverseJoinColumns = { @JoinColumn(name = "role_id") }
    )
    public List<RoleEntity> getRoles() {
        return roles;
    }

    public void setRoles(List<RoleEntity> roles) {
        this.roles = roles;
    }

    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name", nullable = false, length = 25)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Basic
    @Column(name = "password", nullable = false, length = 60)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Basic
    @Column(name = "blocked", nullable = false)
    public boolean getBlocked() {
        return blocked;
    }

    public void setBlocked(boolean blocked) {
        this.blocked = blocked;
    }

    @Basic
    @Column(name = "email", nullable = false, length = 255)
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Mapowanie encji Role


import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name="role")
public class RoleEntity {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @ManyToMany(mappedBy = "roles")
    private List<AccountEntity> accountList;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<AccountEntity> getAccountList() {
        return accountList;
    }
    public void setAccountList(List<AccountEntity> accountList) {
        this.accountList = accountList;
    }

}

Hibernate próbuje wykonać następujące zapytania:

Hibernate: insert into account (blocked, email, name, password, id) values (?, ?, ?, ?, ?)
Hibernate: insert into account_role (user_id, role_id) values (?, ?)

A tutaj stacktrace:

mar 29, 2018 11:31:30 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1452, SQLState: 23000
mar 29, 2018 11:31:30 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Cannot add or update a child row: a foreign key constraint fails (`magazyn`.`account_role`, CONSTRAINT `account_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `account` (`id`))
mar 29, 2018 11:31:30 PM org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl release
INFO: HHH000010: On release of batch it still contained JDBC statements
mar 29, 2018 11:31:30 PM org.hibernate.internal.ExceptionMapperStandardImpl mapManagedFlushFailure
ERROR: HHH000346: Error during managed flush [org.hibernate.exception.ConstraintViolationException: could not execute statement]
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
	at org.springframework.orm.hibernate5.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:247)
	at org.springframework.orm.hibernate5.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:785)
	at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:621)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
	at apteka.service.database.AccountService$$EnhancerBySpringCGLIB$$afde6a86.saveUser(<generated>)
	at apteka.servlet.UserRegisterController.saveUser(UserRegisterController.java:45)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:870)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:776)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:881)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:855)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1115)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
	at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
	at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1332)
	at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:50)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:600)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:474)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
	at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1436)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:493)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3206)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2412)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:156)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
	at org.springframework.orm.hibernate5.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:609)
	... 77 more
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`magazyn`.`account_role`, CONSTRAINT `account_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `account` (`id`))
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:533)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115)
	at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
	at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1826)
	at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2034)
	at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:1970)
	at com.mysql.cj.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5001)
	at com.mysql.cj.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1955)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
	... 94 more

Bardzo bym prosił o wskazówkę, gdyż głowię się nad tym parę h, a pewnie problem banalny. Z góry dziękuję za pomoc.

0

Nie rozumiem, może ja czegoś nie wiem, ale dlaczego w klasie AccountEntity adnotacje masz nad getterami, a nie nad polami, jak w klasie RoleEntity?

0

Zacznijmy od małych porządków. W bazie danych nie masz encji. Masz tabele i wiersze w nich. Encja to pojęcie ze świata programowania obiektowego. Druga rzecz jest taka, że zapewne tabelę account_role stworzyłeś ręcznie - z punktu widzenia Hibernate'a nie potrzebujesz tam generowanego ID. O ile nie jest on Ci potrzebny do jakichś obliczeń robionych "ręcznie", to bym go usunął i zrobił kluczem głównym parę (account_id, role_id).

Ostatnia rzecz dotyczy tego, jak przypisuje się dane w relacji many-to-many. Musisz nawzajem przypisać oba obiekty do siebie po obu stronach relacji:

Account account = getAccount(...);
UserRole role = getRole(...);
account.getRoles().add(account);
role.getAccountList().add(role);

https://www.thoughts-on-java.org/hibernate-tips-map-bidirectional-many-many-association/

0
zyxist napisał(a):

Zacznijmy od małych porządków. W bazie danych nie masz encji. Masz tabele i wiersze w nich. Encja to pojęcie ze świata programowania obiektowego. Druga rzecz jest taka, że zapewne tabelę account_role stworzyłeś ręcznie - z punktu widzenia Hibernate'a nie potrzebujesz tam generowanego ID. O ile nie jest on Ci potrzebny do jakichś obliczeń robionych "ręcznie", to bym go usunął i zrobił kluczem głównym parę (account_id, role_id).

Ostatnia rzecz dotyczy tego, jak przypisuje się dane w relacji many-to-many. Musisz nawzajem przypisać oba obiekty do siebie po obu stronach relacji:

Account account = getAccount(...);
UserRole role = getRole(...);
account.getRoles().add(account);
role.getAccountList().add(role);

https://www.thoughts-on-java.org/hibernate-tips-map-bidirectional-many-many-association/

Dzięki za odpowiedź, zrobiłem tak jak powiedziałeś - faktycznie można wywalić tą kolumnę id z account_roles oraz dodałem

    @Transactional(rollbackOn = Exception.class)
    public void saveUser(AccountEntity user) {
        Session session = sessionFactory.getCurrentSession();

        RoleEntity role = getRoleByName("Pracownik");
        user.setRoles(Collections.singletonList(role));
        role.setAccountList(Collections.singletonList(user));

        session.save(user);
    }

Jednak exception wciąż ten sam :/

ERROR: Cannot add or update a child row: a foreign key constraint fails (`magazyn`.`account_role`, CONSTRAINT `account_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `account` (`id`))

Nie wiem już, co może być przyczyną

EDIT:
Udało się! Nakierował mnie troche lukaszek, który zwrócił uwagę na mapowanie. Oczywiście nie w tym błąd, ale zwróciłem wtedy uwagę na to, że w AccountEntity jest coś takiego:

    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

Zamieniłem to na:

    @Id
    @GeneratedValue
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

Tutaj zaczął wyskakiwać inny błąd:

java.sql.SQLSyntaxErrorException: Table 'magazyn.hibernate_sequence' doesn't exist

Ale sobie z nim poradziłem korzystając z tego
i wszystko działa jak potrzeba :)

Dzięki Wam za pomoc :D

0

W takim razie zrób tak:

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

teraz id powinno się automatycznie generować w MySQL

0

Hej,
podepnę się pod temat żeby nie tworzyć nowego. Mianowicie mam mały projekcik w Spring MVC & Hibernate 5& Spring Security. No wiec chciałem zrobić panel logowania/rejestracji ale nie wiem jak zapisać nowego użytkownika do bazy danych z przypisaniem do niego roli. Czy znacie jakieś materiały lub książki gdzie będzie to dobrze wytłumaczone? No chyba że to jest w miarę proste(siedzę nad tym cały dzień :D) i ktoś tutaj na forum mi jakoś to szybko wyjaśni. No chyba że chodzi o to żeby w servicie podczas zapisywania ręcznie ustawi\c wartość role na ROLE_USER i przypisać tabele role do tabeli user. No ale i tak proszę o pomoc jak to sensownie napisać i jak to powinno być.
Dzięki z góry.

1 użytkowników online, w tym zalogowanych: 0, gości: 1