Hibernate(4.3.0)で問題が発生し、単方向の@OneToManyが重複を返します。
私のデータベース構造(MySQLとInnoDB)。ここで、「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_左外部結合entry_addressaddresses2_on this_.id = addresses2_.id
しかし、次を使用して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]]]
また、FetchType.EAGERの代わりに@OneToMany(fetch = FetchType.LAZY)を使用しようとしました。これは、アドレスが重複している場合と同じ問題です。
++: 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にバグがありますか、それとも何か間違ったことをしていますか?誰かが私が根本的な原因を見つけるのを手伝ってくれることを願っていますか?!ありがとうございました :-)