Hibernate @OneToMany

0

Cześć, mam dwie klasy, encje :

@Entity
public class Player
{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String nick;

    @ManyToOne
    private Team team;
}

i

@Entity
public class Team
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Player> players = new ArrayList<>();

    @Transient
    public void addPlayer(Player player)
    {
        if (player != null)
            players.add(player);
    }
}

gdzieś tam w kontrolerze chcę sobie utworzyć po 1 obiekcie danej klasy i je połączyć dwukierunkowo:

private void addTeams()
    {
        Team team = new Team();
        team.setName("Name"); 

        Player p = new Player();
        team.addPlayer(p); // wstawia nulla do team_id w bazie danych
        // p.addTeam(team); // wstawia dobry klucz do team_id
        teamsService.addTeam(team);  
        playersService.addPlayer(p); 
    } 

I teraz dziwna sprawa.. jeśli łączę przez player.addTeam(team) to działa, tzn zapisuje się w bazie danych w kolumnie team_id odpowiedni klucz. A jeśli zrobię team.addPlayer(player) to wstawia nulla. Już naprawdę nie wiem co tu jest grane.

Robię identycznie jak tu :

@Shalom

0

A kaskady?

0

W tutku co wstawiłem nie ma kaskad no ale ok, wstawiłem

@OneToMany( mappedBy = "team", cascade=CascadeType.ALL )
	private List<Player> players;

I wstawia nie wiem dlaczego dwóch graczy zamiast jednego, ale problemu nie rozwiązuje, a CascadeType.PERSIST nie zmienia nic ..

Może problem w tym, że po session.save() nie robie .flush() ?

Nie do końca rozumiem jeszcze tych kaskad, ale robiłem identycznie jak na tym filmiku i nie działa tak jak jemu ..

0

Problem, jest w tym na co ustawiłeś właściciela asocjacji. Właściciel asocjacji powinien posiadać referencje do encji podrzędnej. Właścielem asocjacji jest klasa player bo posiada Team team a dałeś mappedBy = team; (tzn ze właścicielem asocjacji jest encja posiadająca te pole) I jeżeli dodajesz właściciela asocjacji to encja połączona zostanie wykryta

0

Jak możesz robić IDENTYCZNIE jak w linku YT. Jak po pierwsze przykład jest na czym innym. A po drugie on używa też @JoinColumn przy relacji @ManyToOne, której ty nie masz?
To co tu jest identyczne?

I właśnie w tym przeczuwam problem.

0

On też ma listę @OneToMany i całą strukturę praktycznie taką samą. A @JoinColumn zapewnia mu tylko nadanie nazwy dla kolumny.

Siedzę nad tym od 3 dni i już naprawdę nie wiem czemu to nie działa.

Tutaj link do githuba: https://github.com/skax/eniupage/blob/master/src/main/java/eniupage/web/HomeController.java

W klasie HomeController metoda addPlayers() dobrze wiąże, a addTeams() wstawia mi nulle.

Może w innym miejscu mam jakieś faile ..

0

Mi sie wydaje, ze to jednak trzeba ustawic druzyne dla gracza i gracza w druzynie. Musisz tu i tu ustawic odpowiednie dane.

0

No wtedy to zadziała, ale po co w takim razie określać mappedBy ?

0

Pewnie zeby hibernate wiedzial jak sobie polaczyc/skad wziac dane gdy chcesz pobrac dane dla tych encji? Nie jestem specjalista od JPA/Hibernate niestety ;)

0

No tak, ale nigdzie na żadnym tutku nie widziałem, żeby ktoś wiązał to obustronnie ..

0

Btw. zastanawiales sie nad wywolaniem tych dwoch serwisow?

Player p = new Player();
team.addPlayer(p); // wstawia nulla do team_id w bazie danych
// p.addTeam(team); // wstawia dobry klucz do team_id
teamsService.addTeam(team);  
playersService.addPlayer(p); 

Jesli masz tam kaskade to:

teamsService.addTeam(team);  //zapisze team z playerem
playersService.addPlayer(p);  //nadpisze playera jeszcze raz pustym teamem (bo nie ustawiles?)

Jak juz masz te kaskady to moze jeden serwis powinien zapisywac? Zaloguj jakie SQLe leca.

0

To samo, ciągle ustawia nulle. Tak jakby w ogóle do tej listy nie potrafiło zapisywać ...

0

Piszę z głowy ale całość powinna wyglądać jakoś tak:

 
private void addTeams()
    {
        Team team = new Team();
        team.setName("Name"); 
 
        Player p = new Player();
        p.addTeam(team)
         Session session = sessionFactory.getSession();
         session.beginTransaction();
         session.saveOrUpdate(p);
         session.currentTransaction().commit();
         session.close();
        
    }
0
private void addTeams()
	{
		++t;
		Team team = new Team();
		team.setName("Name " + Integer.toString(t));
		Player p = new Player();
		p.setAge(10 + t);
		p.setDescription("Opis gracza nr " + Integer.toString(t));
		p.setNick("Nick " + Integer.toString(t));

		team.getPlayers().add(p);
		
		teamsService.addTeam(team);
		//playersService.addPlayer(p);

	}

Tym próbuję cały czas, korzystałem trochę z Spring w Praktyce oraz Spring w Akcji, więc mam stamtąd config i całe dao. Niżej wrzucę linki, może w tym jest coś nie tak.

I faktycznie, nie otwieram nigdzie sesji, ale jak działam na encji, która nie ma żadnych powiązań z innymi klasami to tam wszystko śmiga ładnie.

Dao:

package eniupage.domain.repository.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.inject.Inject;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;

import eniupage.domain.repository.Dao;

public abstract class DaoImpl<T> implements Dao<T>
{
	@Inject
	private SessionFactory sessionFactory;

	private Class<T> domainClass;

	protected Session currentSession()
	{
		return sessionFactory.getCurrentSession();

	}

	@SuppressWarnings("unchecked")
	private Class<T> getDomainClass()
	{
		if (domainClass == null)
		{
			ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
			this.domainClass = (Class<T>) thisType.getActualTypeArguments()[0];
		}
		return domainClass;
	}

	private String getDomainClassName()
	{
		return getDomainClass().getName();
	}

	@Transactional
	public void add(T t)
	{
		currentSession().save(t);
	}

	@Transactional
	@SuppressWarnings("unchecked")
	public List<T> getAll()
	{
		return currentSession().createQuery("from " + getDomainClassName()).list();
	}

	@Transactional
	@SuppressWarnings("unchecked")
	public T get(Serializable id)
	{
		return (T) currentSession().get(getDomainClass(), id);
	}

	@Transactional
	public void update(T t)
	{
		currentSession().update(t);
	}

	@Transactional
	@SuppressWarnings("unchecked")
	public T load(Serializable id)
	{
		return (T) currentSession().load(getDomainClass(), id);
	}

	@Transactional
	public long count()
	{
		return (Long) currentSession().createQuery("select count(*) from " + getDomainClassName()).uniqueResult();
	}
}

i config

package eniupage.cfg;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = { "eniupage" }, excludeFilters = {
		@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })
@EnableTransactionManagement
public class RootConfig
{
	@Bean
	public DataSource dataSource()
	{
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName( "com.mysql.jdbc.Driver" );
		dataSource.setUrl( "jdbc:mysql://localhost:3306/eniupage" );
		dataSource.setUsername( "eniupage" );
		dataSource.setPassword( "password" );
		return dataSource;
	}
	
	@Autowired
	@Bean
	public LocalSessionFactoryBean sessionFactory( DataSource dataSource )
	{
		LocalSessionFactoryBean sfb = new LocalSessionFactoryBean();
		sfb.setDataSource( dataSource );
		sfb.setPackagesToScan( new String[] { "eniupage.domain"} );
		Properties props = new Properties();
		props.setProperty( "hibernate.dialect", "org.hibernate.dialect.MySQLDialect" );
		props.setProperty( "hibernate.hbm2ddl.auto", "create-drop" );
		
		sfb.setHibernateProperties( props );
		return sfb;
	}
	
	
	
	@Bean
	public BeanPostProcessor persistenceTranslation()
	{
		return new PersistenceExceptionTranslationPostProcessor();
	}
	
	@Autowired
	@Bean(name = "transactionManager")
	public HibernateTransactionManager getTransactionManager(
	        SessionFactory sessionFactory) {
	    HibernateTransactionManager transactionManager = new HibernateTransactionManager(
	            sessionFactory);
	 
	    return transactionManager;
	}
}
0

Chłopie ale co ty nie rozumiesz ?

 
 team.getPlayers().add(p);
 teamsService.addTeam(team);

Jeżeli sobie chcesz takie coś zrobić to musisz ustawić sobie właściciela encji na pole w klasie team, a masz ustawione na pole team w klasie Player (@OneToMany(mappedBy = "team") - to oznacza ustawienie pola team w klasie ktora adnotujesz na właściciela) I tylko asocjacja będzie działać gdy dodasz właściciela, a nie encje podrzędną.
Zamień:
@OneToMany(mappedBy = "team")
na
@OneToMany

Oraz
@ManyToOne
na
@ManyToOne(mappedBy = "players", cascade = CascadeType.ALL)

I wywołaj

 
private void addTeams()
    {
        ++t;
        Team team = new Team();
        team.setName("Name " + Integer.toString(t));
        Player p = new Player();
        p.setAge(10 + t);
        p.setDescription("Opis gracza nr " + Integer.toString(t));
        p.setNick("Nick " + Integer.toString(t));
 
        team.getPlayers().add(p);
        teamsService.addTeam(team);
    }
0

Faktycznie nie sprawdziłem dokumentacji według niej ustawiasz w tej samej adnotacji jak ustawiłeś tylko zmień na players. http://docs.oracle.com/javaee/6/api/javax/persistence/OneToMany.html I przeczytaj co robi mappedBy ;p

0

Sorry po prostu pisałem bez IDE :P

0

Pokaż te błędy, a obsługę transakcji powinieneś wywoływać.

0

mappedBy reference an unknown target entity property: eniupage.domain.Player.players in eniupage.domain.Team.players

mappedBy musi być na team ustawione ...

0

No dobra czyli jest tak jak na początku w @ManyToOne powinno być mappedBy chociaż zdaję sobie sprawę, że nie istnieje jestem ciekaw więc jak zmienić właściciela encji, poczekam na odpowiedź kogoś bardziej ogarniętego w temacie.

0

Albo po prostu się nie da, i żeby dodać team musisz dodawać osobę, bo tylko jej pole może być właścicielem asocjacji

0

Mi się teraz wydaje, że tu chodzi o tą całą transakcję, żeby zacząć i zakończyć ..

Chociaż dziwi mnie też strasznie, że ja w ogóle nie mam dostępu do team.getPlayers.get(0).getNick() .. w widoku html nie mogę tego w ogóle wyświetlić. Strasznie dziwne akcje tu się robią.

1
@Entity
@Table(name="DEPARTMENT")
public class Department {
 
    @Id
    @GeneratedValue
    @Column(name="DEPARTMENT_ID")
    private Long departmentId;
     
    @Column(name="DEPT_NAME")
    private String departmentName;
     
    @OneToMany(mappedBy="department")
    private Set<Employee> employees;
 
    // Getter and Setter methods
}
@Entity
@Table(name="EMPLOYEE")
public class Employee {
 
    @Id
    @GeneratedValue
    @Column(name="employee_id")
    private Long employeeId;
     
    @Column(name="firstname")
    private String firstname;
     
    @Column(name="lastname")
    private String lastname;
     
    @Column(name="birth_date")
    private Date birthDate;
     
    @Column(name="cell_phone")
    private String cellphone;
 
    @ManyToOne
    @JoinColumn(name="department_id")
    private Department department;
     
    public Employee() {
         
    }
     
    public Employee(String firstname, String lastname, String phone) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.birthDate = new Date(System.currentTimeMillis());
        this.cellphone = phone;
    }
 
    // Getter and Setter methods
}
public class Main {
 
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
 
        SessionFactory sf = HibernateUtil.getSessionFactory();
        Session session = sf.openSession();
        session.beginTransaction();
 
        Department department = new Department();
        department.setDepartmentName("Sales");
        session.save(department);
         
        Employee emp1 = new Employee("Nina", "Mayers", "111");
        Employee emp2 = new Employee("Tony", "Almeida", "222");
 
        emp1.setDepartment(department);
        emp2.setDepartment(department);
         
        session.save(emp1);
        session.save(emp2);
 
        session.getTransaction().commit();
        session.close();
    }
}

I to hula. Nie za bardzo mam czas odpalić Twój kod więc posiłkuję się tym co wkleiłem.

0
Bambo napisał(a):

Mi się teraz wydaje, że tu chodzi o tą całą transakcję, żeby zacząć i zakończyć ..

Chociaż dziwi mnie też strasznie, że ja w ogóle nie mam dostępu do team.getPlayers.get(0).getNick() .. w widoku html nie mogę tego w ogóle wyświetlić. Strasznie dziwne akcje tu się robią.

Wydaje mi sie, ze masz transakcje, pewnie jest skonfigurowana dobrze i @Transactional dziala jak nalezy. Zeby byc pewnym mozesz sprawdzic w jakiejs metodzie wywolanie: TransactionSynchronizationManager.isActualTransactionActive(), sprawdz co zwraca.

Jesli chodzi o ten brak dostepu to zapewne jest tam lazy loading, po zamknieciu transakcji juz nie da rady dociagnac tych danych.

1

tak robisz i koniec:

public void addPlayer(Player player)
{
    players.add(player);
    player.setTeam(this);
}

zobacz przykłady w dokumentacji Hibernate:
http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#collections

1

czyli to co na początku mówiłem, cytując:

Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.

1

W sumie, chyba jednak wychodzi, że nie da się zrobić tego co chcesz zrobić. Hibernate, specjalnie nie zezwolił na mappedBy w @ManyToOne ponieważ ustawienie właściciela asocjacji na listę powodowało by problem. Powiedzmy, dodamy do List, obiekt i zapiszemy obiekt z Listem do bazy tak jak chciales pozniej dodam obiekt ktory jest w liscie,który wsadziłeś do jeszcze innej listy co wtedy ? Jak powinna zostać ustawiona asocjacja tego obiektu ? Do pierwszego obiektu z listą czy do drugiegiego - przecież jest w obu listach ten obiekt. Tzn w twojej aplikacji

Team t1 = new Team();
Team t2 = new Team();
Player player = new Player();
t1.add(player);
t2.add(player);
 

I zapisujesz w bazie, co będzie gdy wyjmiesz obiekt player z bazy ? player.team będzie równe t1 czy t2 ?

To takie spekulacje, było chyba właśnie tak kiedyś opisane w książce której czytałem o hibernate, poza tym w tym typie asoscjaji ManyToOne klucz znajduje się zawsze w jednej tabeli w drugiej nie może. Czyli jedna tabela jest właścicielem asocjacji strona many zawierająca klucze. Bo jak wpisać do one wiele kluczy, żeby zawierały wiele rekordów drugiej tabeli ? nie da się bez dodatkowe tablicy.

0

Ale nie wiem, spekuluje jakby co zeby znowu w blad nie wprowadzac

0

No dobra, to tu już wszystko wiem, ale jak teraz zrobić, żeby w widoku móc wywołać ${team.getPlayers().get(0).getNick() ? ${team.name} działa ok :) Dostaję takie coś:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

Ok team. Zmienić FetchType z LAZY NA EAGER

0

dodaj do @OneToMany fetch=FetchType.EAGER

Przy czym, jesli tylko jednego gracza wyswietlasz to zastanow sie czy to ma sens ;)

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