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

pit*_*chr 6 java mysql spring hibernate

我遇到了Hibernate(4.3.0)的问题,其中单向@OneToMany返回重复项。

我的数据库结构(带有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`);
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是“ 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是由Hibernate完成的查询(看起来不错!):Hibernate:选择this_.id作为id0_1_,this_.name作为name0_1_,addresses2_.id作为id0_3_,addresses2_.id作为id1_3_,addresses2_.id作为id1_0_,addresss2..line作为条目this_第1_0_行,位于this_.id = addresses2_.id上的外部联接entry_addressaddresss2_

MySQL输出

但是如果我使用以下命令运行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());

    }
}
Run Code Online (Sandbox Code Playgroud)
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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

控制台日志为:

++: 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]]]
Run Code Online (Sandbox Code Playgroud)

我还尝试使用@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]]]
Run Code Online (Sandbox Code Playgroud)

期望

这是我的期望(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]]]
Run Code Online (Sandbox Code Playgroud)

Hibernate中是否有错误,或者我做错了什么?希望有人可以帮助我找到根本原因吗?谢谢 :-)

Dav*_* L. 7

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

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

另外,更改addressesSet

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

编辑:

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

  • 从列表切换到集合救了我的命。 (2认同)