views:

652

answers:

3

Hi,

I have a one way @OneToMany relationship between a Team and Player class. I would like to save a Team object among your Players. Player's identifier IS COMPOUND by Team foreign key and list index as follows. I have a mapping like this one because i need to save Team and your Players ate the same time.

@Entity
public class Team {

    @Id
    @GeneratedValue
    private Integer id;

    @CollectionOfElements
    @JoinTable(
        name="PLAYER",
        joinColumns=@JoinColumn(name="TEAM_ID"))
    @IndexColumn(name="PLAYER_INDEX")
    private List<Player> playerList = new ArrayList<Player>();

}

@Embeddable
public class Player {

   // Player's getter's and setter's

}

So if i use the following

Team team = new Team();

team.getPlayerList().add(new Player());
team.getPlayerList().add(new Player());

session.save(team); // It works!

It happens whether you use @CollectionsOfElements, Player class need a @Embeddable annotation, not a @Entity. JPA does not allows @Entity and @Embeddable at the same time. Player is also a @Entity - it has relationship with other entities.

Has someone an idea about i could save a Team and a Player (ONE WAY RELATIONSHIP) by using CascadeType.PERSIST with @Entity in the Player class instead of @Embeddable ?

Remember that COMPOUND primary key needs to be assigned before saving but Team's identifier and PlayerList index position could play the role of Player's compound primary key

regards,

A: 

You've made some mistakes I feel.

@Embedded is a way to represent an object/component made from selected fields in table. You can use it to represent composite keys but you need to also use @EmbeddedId.

Looking at what you are trying to achieve I feel you can get there with a much simpler mapping. (a few parts omitted for brevity)

@Entity
public class Team {

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

}

@Enity
public class Player {

  @ManyToOne
  @JoinColumn(name="TEAM_ID")
  private Team team;

}

If Player is a join/link table you can use an @Embedded static class to represent the composite key, see the book Java Persistence with JPA for further information on this.

JamesC
A: 

Hi,

Although i have chosen Maarten Winkels's answer as the best, there are some issues with regard at it.

1º It uses a Team object as a key in Player compound primary key

@Embeddable
public class PlayerId implements Serializable {

    @ManyToOne
    private Team team;

Because it, he have to map List in Team class according to

@OneToMany(cascade=CascadeType.ALL, mappedBy="id.team")
@IndexColumn(name="PLAYER_IDX")
private List<Player> players = new ArrayList<Player>();

Notice mappedBy above

2º Hibernate team says it’s usually inconvenient to have an association in a composite identifier class and it has limitations in queries: you can’t restrict a query result in HQL or Criteria across a reference join, so this approach isn’t recommended except in special circumstances. In fact, it is not a good practice.

You can avoid it because according to JPA with Hibernate book

If no identifier generator is declared, Hibernate expects the application to take care of the primary key value assignment.

Ok but Team id has a strategy declared @GeneratedValue. This way, Hibernate will pick up the best strategy according to target database.

Now take a look at the following

@Entity
public class Team {

    // You should use a MutableInt because Integer is immutable
    // We need both Team's id and PlayerId's teamId references the same instance
    // So when Hibernate assign a value to Team's id, PlayerId's teamId property will reference the same instance referenced by Team's id
    private MutableInt id = new MutableInt(-1);

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id.intValue();
    }

    public void setId(Integer id) {
        this.setValue(id);
    }

    private List<Player> playerList;

    // Is mappedBy lost ?
    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="TEAM_ID", insertable=false, updateable=false)
    public List<Player> getPlayerList() {
        return this.playerList;
    }

    public void addPlayer(Player player) {
        // is this reference lost ?
        player.setId(new PlayerId(this.id, getPlayerList().size()));

        childList.add(child);
    }

}

So i have assigned a value to Team's id field in order to reference Player's compound primary key (Hiberante will not save it because of generator strategy) as follows

@Embeddable
public class PlayerId {

    private MutableInt teamId;

    @Column(name="TEAM_ID")
    public Integer getTeamId() {
        return this.teamId.intValue();
    }

    private Integer playerIndex;

    @Column(name="PLAYER_INDEX")
    public Integer getTeamId() {
        return this.playerIndex;
    }

    public PlayerId() {}

    public PlayerId(MutableInt teamId, Integer playerIndex) {
        this.teamId = teamId;
        this.playerIndex = playerIndex;
    }

    // equals and hashcode

}

Now it works fine without any Team reference in Player class nor mappedBy. You can test if you want. One way relationship.

regards,

Arthur Ronald F D Garcia
please don't post additional information this way; you should only post an answer if it's an answer. other people may not see all answers (especially if there are a lot of others). edit your original question instead.
Jason S
+3  A: 

The below solution shows a composite key for Player that consists of Team and the position in the list of players in that team. Saves cascade from team to players.

Team.java

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.hibernate.annotations.IndexColumn;

@Entity
public class Team implements Serializable {

    @Id @GeneratedValue private Long id;

    @Version private int version;

    @OneToMany(cascade=CascadeType.ALL, mappedBy="id.team")
    @IndexColumn(name="PLAYER_IDX")
    private List<Player> players = new ArrayList<Player>();

    private String name;

    protected Team() {}

    public Team(String name) {
     this.name = name;
    }

    public boolean addPlayer(Player player) {
     boolean result = players.add(player);
     if (result) {
      player.setPlayerId(new PlayerId(this, players.size() - 1));
     }
     return result;
    }

    public List<Player> getPlayers() {
     return players;
    }

    @Override
    public String toString() {
     return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("name", name).append("players", players).toString();
    }
}

Player.java

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

@Entity
public class Player implements Serializable {

    @Id private PlayerId id;

    @Version private int version;

    void setPlayerId(PlayerId id) {
     this.id = id;
    }

    @Override
    public String toString() {
     return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("number", id.getNumber()).toString();
    }

}

PlayerId.java

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.ManyToOne;

import org.apache.commons.lang.builder.HashCodeBuilder;

@Embeddable
public class PlayerId implements Serializable {

    @ManyToOne
    private Team team;

    @Column(name="PLAYER_IDX", insertable=false, updatable=false)
    private int number;

    protected PlayerId() {}

    PlayerId(Team team, int number) {
     this.team = team;
     this.number = number;
    }

    public int getNumber() {
     return number;
    }

    @Override
    public boolean equals(Object obj) {
     if (obj == null) {
      return false;
     } else if (obj == this) {
      return true;
     } if (obj instanceof PlayerId) {
      PlayerId other = (PlayerId) obj;
      return other.team.equals(this.team) && other.number == this.number; 
     }
     return false;
    }

    @Override
    public int hashCode() {
     return new HashCodeBuilder().append(team).append(number).toHashCode();
    }

}

This test below:

public void testPersistTeamAndPlayers() throws Exception {
 Team team = new Team("My Team");
 team.addPlayer(new Player());
 team.addPlayer(new Player());

 AnnotationConfiguration configuration = new AnnotationConfiguration();
 configuration.addAnnotatedClass(Team.class);
 configuration.addAnnotatedClass(Player.class);
 configuration.configure();

 SessionFactory sessionFactory = configuration.buildSessionFactory();
 Session session;
 session = sessionFactory.openSession();
 Transaction transaction = session.beginTransaction();
 session.save(team);
 transaction.commit();
 session.close();

 session = sessionFactory.openSession();
 @SuppressWarnings("unchecked") List<Team> list = session.createCriteria(Team.class).list();
 assertEquals(1, list.size());

 Team persisted = list.get(0);
 System.out.println(persisted);

gives the following log output:

12:37:17,796 DEBUG [SchemaExport, SchemaExport.execute] 
    create table Player (
        PLAYER_IDX integer not null,
        version integer not null,
        team_id bigint,
        primary key (PLAYER_IDX, team_id)
    )
12:37:17,796 DEBUG [SchemaExport, SchemaExport.execute] 
    create table Team (
        id bigint generated by default as identity (start with 1),
        name varchar(255),
        version integer not null,
        primary key (id)
    )
12:37:17,796 DEBUG [SchemaExport, SchemaExport.execute] 
    alter table Player 
        add constraint FK8EA38701AA5DECBA 
        foreign key (team_id) 
        references Team
12:37:17,812 INFO  [SchemaExport, SchemaExport.importScript] Executing import script: /import.sql
12:37:17,812 INFO  [SchemaExport, SchemaExport.execute] schema export complete
12:37:17,859 DEBUG [SQL, SQLStatementLogger.logStatement] 
    insert 
    into
        Team
        (id, name, version) 
    values
        (null, ?, ?)
12:37:17,875 DEBUG [SQL, SQLStatementLogger.logStatement] 
    call identity()
12:37:17,875 DEBUG [SQL, SQLStatementLogger.logStatement] 
    select
        player_.PLAYER_IDX,
        player_.team_id,
        player_.version as version1_ 
    from
        Player player_ 
    where
        player_.PLAYER_IDX=? 
        and player_.team_id=?
12:37:17,890 DEBUG [SQL, SQLStatementLogger.logStatement] 
    select
        player_.PLAYER_IDX,
        player_.team_id,
        player_.version as version1_ 
    from
        Player player_ 
    where
        player_.PLAYER_IDX=? 
        and player_.team_id=?
12:37:17,890 DEBUG [SQL, SQLStatementLogger.logStatement] 
    insert 
    into
        Player
        (version, PLAYER_IDX, team_id) 
    values
        (?, ?, ?)
12:37:17,890 DEBUG [SQL, SQLStatementLogger.logStatement] 
    insert 
    into
        Player
        (version, PLAYER_IDX, team_id) 
    values
        (?, ?, ?)
12:37:17,906 DEBUG [SQL, SQLStatementLogger.logStatement] 
    select
        this_.id as id0_0_,
        this_.name as name0_0_,
        this_.version as version0_0_ 
    from
        Team this_
12:37:17,937 DEBUG [SQL, SQLStatementLogger.logStatement] 
    select
        players0_.team_id as team3_1_,
        players0_.PLAYER_IDX as PLAYER1_1_,
        players0_.PLAYER_IDX as PLAYER1_1_0_,
        players0_.team_id as team3_1_0_,
        players0_.version as version1_0_ 
    from
        Player players0_ 
    where
        players0_.team_id=?
Team[name=My Team,players=[Player[number=0], Player[number=1]]]

The last line shows the toString for Team and Player, which shows how the numbers are assigned (the index of the list). Other entities can refer the Player (by team_id and player_idx).

Maarten Winkels
Congratulations, Maarten. Good answer.
Arthur Ronald F D Garcia