java 休眠问题:@OneToMany 注释返回重复项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13256569/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 12:03:00  来源:igfitidea点击:

Hibernate issue: @OneToMany annotation returns duplicates

javamysqlspringhibernate

提问by pitschr

I am facing a problem with Hibernate (4.3.0) where as unidirectional @OneToMany returns duplicates.

我正面临 Hibernate (4.3.0) 的问题,因为单向 @OneToMany 返回重复项。

My database structure (MySQL with InnoDB) where as 'entry' table has a 1:N relationship with 'entry_address' table. The 'entry' table is the main table and 'entry_address' is a sub-table of 'entry' table.

我的数据库结构(带有 InnoDB 的 MySQL),其中 'entry' 表与 'entry_address' 表具有 1:N 关系。“entry”表是主表,“entry_address”是“entry”表的子表。

CREATE TABLE IF NOT EXISTS `entry` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(500) NOT NULL,
  `active` int(1) NOT NULL DEFAULT '0',
  `modifiedTS` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
  `createdTS` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

INSERT INTO `entry` (`id`, `name`, `active`, `modifiedTS`, `createdTS`) VALUES
(1, 'Test1', 0, '2012-11-05 13:41:03', '2012-11-01 10:11:22'),
(2, 'Test2', 1, '2012-11-05 11:19:37', '2012-11-01 10:11:33'),
(3, 'Test3', 1, '2012-11-05 11:19:37', '2012-11-01 10:11:44');

CREATE TABLE IF NOT EXISTS `entry_address` (
  `id` int(10) unsigned NOT NULL,
  `precedence` int(1) NOT NULL DEFAULT '0',
  `line` varchar(255) DEFAULT NULL,
  `line2` varchar(255) DEFAULT NULL,
  `street` varchar(255) DEFAULT NULL,
  `street2` varchar(255) DEFAULT NULL,
  `zip` int(5) DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `country` varchar(255) DEFAULT NULL,
  UNIQUE KEY `entry_address_uq` (`id`,`precedence`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `entry_address` (`id`, `precedence`, `line`, `line2`, `street`, `street2`, `zip`, `city`, `country`) VALUES
(1, 0, 'Line4.1', 'Line4.2', 'Street4.1', 'Street4.2', 9488, 'Schellenberg', 'Liechtenstein'),
(2, 10, 'Line1.1', 'Line1.2', 'Street1.1', 'Street1.2', 9492, 'Eschen', 'Liechtenstein'),
(2, 20, 'Line2.1', 'Line2.2', 'Street2.1', 'Street2.2', 9490, 'Vaduz', 'Liechtenstein'),
(2, 30, 'Line3.1', 'Line3.2', 'Street3.1', 'Street3.2', 9494, 'Schaan', 'Liechtenstein'),
(3, 10, 'Line5.1', 'Line5.2', 'Street5.1', 'Street5.2', 9492, 'Eschen', 'Liechtenstein'),
(3, 20, 'Line6.1', 'Line6.2', 'Street6.1', 'Street6.2', 9492, 'Eschen', 'Liechtenstein');

ALTER TABLE `entry_address`
  ADD CONSTRAINT `entry_address_fk` FOREIGN KEY (`id`) REFERENCES `entry` (`id`);

Here's the minimal code of "entry" entity.

这是“入口”实体的最小代码。

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

@Entity
@Table(name = "entry")
public class Entry {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Integer id;

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

    @OneToMany(fetch=FetchType.EAGER)
    @JoinColumn(name = "id")
    private Collection<EntryAddress> addresses;

    @Override
    public String toString() {
        return String.format("Entry [id=%s, name=%s, addresses=%s]", id, name, addresses);
    }
}

Here's the minimal code of "entry_address" entity:

这是“entry_address”实体的最小代码:

import javax.persistence.*;

@Entity
@Table(name = "entry_address")
public class EntryAddress {
    @Id
    @Column(name = "id")
    private Integer id;

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

    @Override
    public String toString() {
        return String.format("EntryAddress [line=%s]", line);
    }
}

This is the query done by Hibernate (looks good!): Hibernate: select this_.id as id0_1_, this_.name as name0_1_, addresses2_.id as id0_3_, addresses2_.id as id1_3_, addresses2_.id as id1_0_, addresses2_.line as line1_0_ from entry this_ left outer join entry_address addresses2_ on this_.id=addresses2_.id

这是 Hibernate 完成的查询(看起来不错!): Hibernate:选择 this_.id 作为 id0_1_,this_.name 作为 name0_1_,addresses2_.id 作为 id0_3_,addresses2_.id 作为 id1_3_,addresses2_.id 作为 id1_0_,addresses2_.line 作为line1_0_ from entry this_ 左外连接 entry_addressaddresses2_ on this_.id=addresses2_.id

MySQL Output

MySQL 输出

But if I run JUnit using:

但是,如果我使用以下命令运行 JUnit:

import java.util.Collection;

import junit.framework.Assert;
import li.pitschmann.transaction.dao.EntryDao;
import li.pitschmann.transaction.entity.Entry;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;

@ContextConfiguration(locations={"file:**/web-spring.xml"})
public class EntryDaoTest extends AbstractTransactionalJUnit4SpringContextTests {
    @Autowired
    private EntryDao entryDao;

    @Test
    public void findAllEntries() {
        Collection<Entry> entries = entryDao.findEntries();

        Assert.assertNotNull(entries);

        for (Entry e : entries) {
            System.out.println("++: " + e);
        }
        // Assert.assertEquals(3, entries.size());

    }
}
import java.util.Collection;

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

import li.pitschmann.transaction.dao.EntryDao;
import li.pitschmann.transaction.entity.Entry;

public class EntryDaoImpl implements EntryDao {
    private SessionFactory sessionFactory;

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Transactional
    public Collection<Entry> findEntries() {
        return sessionFactory.getCurrentSession().createCriteria(Entry.class).list();
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

Spring XML (most important part, Spring 3.1.2.RELEASE):

Spring XML(最重要的部分,Spring 3.1.2.RELEASE):

<tx:annotation-driven transaction-manager="transactionManager" />
<context:annotation-config />
<!-- MySQL DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/entry_db" />
    <property name="user" value="root" />
    <property name="password" value="" />
</bean>
<!-- Session Factory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan">
        <list>
            <value>li.pitschmann.transaction.entity</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

The console log is:

控制台日志是:

++: Entry [id=1, name=Test1, addresses=[EntryAddress [line=Line4.1]]]
++: Entry [id=2, name=Test2, addresses=[EntryAddress [line=Line1.1], EntryAddress [line=Line1.1], EntryAddress [line=Line1.1]]]
++: Entry [id=2, name=Test2, addresses=[EntryAddress [line=Line1.1], EntryAddress [line=Line1.1], EntryAddress [line=Line1.1]]]
++: Entry [id=2, name=Test2, addresses=[EntryAddress [line=Line1.1], EntryAddress [line=Line1.1], EntryAddress [line=Line1.1]]]
++: Entry [id=3, name=Test3, addresses=[EntryAddress [line=Line5.1], EntryAddress [line=Line5.1]]]
++: Entry [id=3, name=Test3, addresses=[EntryAddress [line=Line5.1], EntryAddress [line=Line5.1]]]

I also tried to use @OneToMany(fetch=FetchType.LAZY) instead of FetchType.EAGER - same issue with duplicate addresses.

我还尝试使用 @OneToMany(fetch=FetchType.LAZY) 而不是 FetchType.EAGER - 重复地址的相同问题。

++: Entry [id=1, name=Test1, addresses=[EntryAddress [line=Line4.1]]]
++: Entry [id=2, name=Test2, addresses=[EntryAddress [line=Line1.1], EntryAddress [line=Line1.1], EntryAddress [line=Line1.1]]]
++: Entry [id=3, name=Test3, addresses=[EntryAddress [line=Line5.1], EntryAddress [line=Line5.1]]]

Expectation

期待

This is my expecation (3 entry objects with different addresses):

这是我的期望(具有不同地址的 3 个条目对象):

++: Entry [id=1, name=Test1, addresses=[EntryAddress [line=Line4.1]]]
++: Entry [id=2, name=Test2, addresses=[EntryAddress [line=Line1.1], EntryAddress [line=Line2.1], EntryAddress [line=Line3.1]]]
++: Entry [id=3, name=Test3, addresses=[EntryAddress [line=Line5.1], EntryAddress [line=Line6.1]]]

Is there a bug in Hibernate or am I doing something wrong? Hope someone can help me to find the root cause?! Thank you :-)

Hibernate 中是否存在错误或我做错了什么?希望有人能帮我找到根本原因?!谢谢 :-)

采纳答案by Dave L.

You might need to modify your method like so:

您可能需要像这样修改您的方法:

@SuppressWarnings("unchecked")
@Transactional
public Collection<Entry> findEntries() {
    return sessionFactory.getCurrentSession()
      .createCriteria(Entry.class)
      .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
      .list();
}

Also, change addressesto a Set:

另外,更改addressesSet

@OneToMany(fetch=FetchType.EAGER)
@JoinColumn(name = "id")
private Set<EntryAddress> addresses;

Edit:

编辑:

Oh...In EntryAddressyou have iddefined as the @Idbut it is not unique. You should make idthe primary key and have it auto increment like you do in Entry. Then create another field in EntryAddressthat is the foreign key to Entrycalled something like entry_id.

哦...在EntryAddress你已经id定义为@Id但它不是唯一的。您应该制作id主键并让它像在Entry. 然后在EntryAddress其中创建另一个字段,该字段是Entry调用类似entry_id.