我遇到了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_,条目为this_ left outer join entry_address addresses2_ on this_.id = addresses2_.id
但是如果我使用:
运行JUnitimport 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中是否有错误或我做错了什么?希望有人能帮助我找到根本原因?!谢谢: - )
答案 0 :(得分:3)
您可能需要像这样修改方法:
@SuppressWarnings("unchecked")
@Transactional
public Collection<Entry> findEntries() {
return sessionFactory.getCurrentSession()
.createCriteria(Entry.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
}
另外,将addresses
更改为Set
:
@OneToMany(fetch=FetchType.EAGER)
@JoinColumn(name = "id")
private Set<EntryAddress> addresses;
编辑:
哦......在EntryAddress
id
中,@Id
定义为id
但不是唯一的。您应该将Entry
作为主键并使其自动增加,就像在EntryAddress
中一样。然后在Entry
中创建另一个字段,该字段是entry_id
的外键,称为{{1}}。