我有实体(这是我的简化版),DB中定义的Entity1
和Entity2
之间没有关系。我需要选择(使用外部联接)这样的记录,其中Entity1
和Entity2
以及国家/地区的代码是相同的:
非常简单的JPA查询:
select a, b
from Entity1 a
left join Entity2 b
on a.code = b.code
and a.country = b.country
但是当我这样做时,会生成错误的查询:
SELECT t0.ID, t0.CODE, t0.COUNTRY_ID, t1.ID, t1.CODE, t1.COUNTRY_ID
FROM tab1 t0
LEFT OUTER JOIN tab2 t1 ON ((t0.CODE = t1.CODE) AND (t0.COUNTRY_ID = t2.ID))
, c t2
WHERE (t2.ID = t1.COUNTRY_ID)
我的意见是,正确的查询包含
LEFT OUTER JOIN t2 t1 ON ((t0.CODE = t1.CODE) AND (t0.COUNTRY_ID = t1.COUNTRY_ID ))
没有加入t2
。
好的,重现问题的所有细节如下:
我有pom.xml的maven项目:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>betlista</groupId>
<artifactId>jpa.eclipselink.tests</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<spring.version>3.2.6.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.1</version><!-- I tried also 2.5.2, but the result is the same -->
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.181</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
我的实体看起来像:
package entity; // in "src/main/java"
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table( name = "c" )
public class Country {
@Id
long id;
@Column
private String code;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
package entity; // in "src/main/java"
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
@Entity
@Table( name = "tab1" )
public class Entity1 {
@Id
private long id;
@Column
private String code;
@JoinColumn(name = "COUNTRY_ID")
private Country country;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
}
package entity; // in "src/main/java"
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
@Entity
@Table( name = "tab2" )
public class Entity2 {
@Id
private long id;
@Column
private String code;
@JoinColumn(name = "COUNTRY_ID")
private Country country;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
}
我的JUnit测试:
package tests; // in "src/test/java"
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:commons-test.xml" })
public class LeftJoinTest extends AbstractTransactionalJUnit4SpringContextTests {
@PersistenceContext
protected EntityManager em;
@Test
public void test() {
Assert.assertNotNull(em);
}
@Test
public void testLeftJoin() {
StringBuilder queryString = new StringBuilder();
queryString.append(" select a, b " );
queryString.append(" from Entity1 a ");
queryString.append(" left join Entity2 b " );
queryString.append(" on a.code = b.code ");
queryString.append(" and a.country = b.country ");
Query query = em.createQuery(queryString.toString());
List list = query.getResultList();
Assert.assertEquals( 1, list.size() );
Object[] oa = (Object[])list.get(0);
Assert.assertEquals( 2, oa.length );
System.out.println(list);
}
}
其中Spring配置文件(src / test / resources中的commons-test.xml
)是
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:component-scan base-package="entity" />
<context:annotation-config />
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:ddl.sql" />
</jdbc:embedded-database>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="eclipseLinkPu2" />
<property name="packagesToScan" value="entity" />
<property name="jpaVendorAdapter" ref="eclispeLinkJpaVendor" />
<property name="dataSource" ref="dataSource" />
<property name="jpaPropertyMap">
<map>
<entry key="eclipselink.weaving" value="false" />
</map>
</property>
</bean>
<bean id="eclispeLinkJpaVendor"
class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
<property name="generateDdl" value="false" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
和内存数据库创建脚本(也在src / test / resources中)是
create table c (
id number,
code varchar2(20)
);
create table tab1 (
id number,
code varchar2(30),
country_id number
);
create table tab2 (
id number,
code varchar2(30),
country_id number
);
-- ****************
-- *** data ***
-- ****************
insert into c values( 1, 'US' );
insert into tab1 values ( 1, 'code1', 1 );
insert into tab2 values ( 1, 'code1', 1 );