7

我遇到了 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`);

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

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);
    }
}

这是“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);
    }
}

这是 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_ left external join entry_addressaddresses2_ on this_.id=addresses2_.id

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());

    }
}
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(最重要的部分,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>

控制台日志是:

++: 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]]]

我还尝试使用 @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]]]

期待

这是我的期望(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]]]

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

4

1 回答 1

7

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

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

另外,更改addressesSet

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

编辑:

哦...在EntryAddressid定义为 the@Id但它不是唯一的。您应该制作id主键并让它像您在Entry. 然后在其中创建另一个字段,EntryAddress该字段是外键,Entry称为entry_id.

于 2012-11-06T17:59:50.517 回答