views:

397

answers:

2

Hi all,

I've got a pretty special setup: I create all the classes in Java, connect them in my application (several ManyToOne-relationships).

Then, I'd like to iterate over my objects and save them into the database. Sometimes, an object is already in the database, then it should not be persisted again.

I implemented the hashCode() and equals()-method correct, but my em.merge() inserts the objects nevertheless.

Again:

I create some objects, i.e. I create some player and set in which team they are. the teams may be different objects in Java, but according to their "equals"-method, they are the same. So if I save a player, the team should be saved accordingly (that works), but if the team exists in the database (according to the equals-method), it should not be inserted again, but the relationship should be set, of course.

What I'm I doing wrong? More information needed?

    private static void saveModels(final Set<?> models) {
    EntityManagerFactory factory = null;

    factory = Persistence.createEntityManagerFactory("sqlite");

    EntityManager manager = factory.createEntityManager();

    manager.getTransaction().begin();

    for (Object object : models) {
        manager.merge(object);
    }

    manager.getTransaction().commit();

    manager.close();
    factory.close();
}

edit

@Entity
public class Team {

    private long id;
    private String description;

    @Id
    @GeneratedValue
    public long getId() {
        return id;
    }

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

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description= description;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + description.length();
        return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Team other = (Team) obj;
        if (!description.equals(other.getDescription())) {
            return false;
        }
        return true;
    }
}


@Entity
public class Player {
    private long id;
    private Team team;
    private String name;

    @Id
    @GeneratedValue
    public long getId() {
        return id;
    }

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

    @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, targetEntity = Team.class)
    @JoinColumn(name = "team_id")
    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }

    public String getName() {
        return name;
    }

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


    @Override
    public int hashCode() {
        return name.length();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Player)) {
            return false;
        }

        Player other = (Player) obj;
        return other.getName().equals(name);
    }
}
A: 

Try making the relationship bi-directional by given the Team class a Collection of Players as a field. This would be annotated like so

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, targetEntity = Team.class)
Collection<Player> getPlayers() {
  return this.players;
}
mR_fr0g
do I have to set the players in the team class in my application, too? because that would be pretty hard, as I'm creating several team classes which are semantically the same... hope you understand, what I mean...
swalkner
"several team classes".. perhaps you should use inheritance there? anyway, we are not seeing the full picture yet.
Bozho
"several team classes" - they are all the same class, but different objects, created with "new Team()"; but as they all contain the same description, they should only be saved once into the database.
swalkner
+1  A: 

JPA uses the @Id field to do the merges, it won't use the equals and hashCode methods to check if an entity already exists in the database.

Add a @OneToMany mapping on the Team, like Bozho suggests, although I'd do it like this.

@OneToMany(cascade = { CascadeType.ALL })
private List<Player> players = new ArrayList<Player>();

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

public Collection<Player> getPlayers() {
    return new ArrayList<Player>(this.players);
}

As you're doing merging lots of entites at once, I'm assuming you're doing a bulk import from a CSV or something. Then, rather than creating a new Team/Player for every line in the CSV, keep a Map of Teams keyed by the name and just add the players to the relevant Team.

So, instead of

Team t = new Team();
t.setName(teamName)
Player p = new Player();
p.setName(playerName);
p.setTeam(t);

Do

Map<String, Team> teams = new HashMap<String,Team>();

...

if (!teams.containsKey(teamName)) {
    Team t = new Team();
    t.setDescription(teamName);
    teams.put(teamName, t)
}

Player p = new Player();
p.setName(p);
teams.get(teamName).addPlayer(p);

...

saveModels(teams.values());
Dan Godfrey
in my opinion not the cleanest approach, but it seems to work, thank you!
swalkner