2014-04-14 36 views
1

我想創建一個使用Spring-data-jpa,hibernate,spring-data,H2(用於tesitng)和最終Postgress(生產)的Parent-Child關係。spring-data-jpa @OneToMany失敗,延遲初始化

以下是h2.sql定義的表:

CREATE TABLE IF NOT EXISTS Menu (
    menuId bigint(11) NOT NULL AUTO_INCREMENT, 
    displayText varchar (100) DEFAULT NOT NULL, 
    displayOrder int default NULL 
); 


CREATE TABLE IF NOT EXISTS MenuItem (
    menuItemId bigint(11) NOT NULL AUTO_INCREMENT, 
    displayText varchar (100) DEFAULT NOT NULL, 
    path varchar (50) NULL, 
    toolTip varchar (500) DEFAULT NOT NULL, 
    displayOrder int default NULL, 
    callType varchar (50) DEFAULT NOT NULL 
); 

我有兩個簡單的實體:

@Entity 
public class Menu { 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private long menuId; 

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "menu") 
    private List<MenuItem> menuItems = new ArrayList<MenuItem>(); 

    private String displayText; 
    private int displayOrder; 

@Entity 
public class MenuItem { 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private long menuItemId; 

    private String displayText; 
    private String path; 
    private String toolTip; 
    private int displayOrder; 

    @ManyToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "menuId", nullable = false) 
    private Menu menu; 

    @Enumerated(EnumType.STRING) 
    @Column(name = "callType", nullable = false) 
    private HttpType callType; 

我有一個應用程序:

@Configuration 
@ComponentScan 
@EnableJpaRepositories 
@EnableTransactionManagement 
@EnableAutoConfiguration 
public class Application { 

    public static void main(String[] args) { 
     ApplicationContext MyApplication = SpringApplication.run(Application.class, args); 
    } 
} 

和一個配置類:

@Configuration 
public class MyConfiguration { 
@Bean 
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { 
     LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean(); 
     lef.setDataSource(dataSource); 
     lef.setJpaVendorAdapter(jpaVendorAdapter); 
     lef.setPackagesToScan("com.xxx.yyy"); 
     return lef; 
    } 

    @Bean 
    public JpaVendorAdapter jpaVendorAdapter() { 
     HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); 
     hibernateJpaVendorAdapter.setShowSql(true); 
     hibernateJpaVendorAdapter.setGenerateDdl(true); 
     hibernateJpaVendorAdapter.setDatabase(Database.H2); 
     return hibernateJpaVendorAdapter; 
    } 

    @Bean 
    public PlatformTransactionManager transactionManager() { 
     return new JpaTransactionManager(); 
    } 

    @Bean 
    public DataSource dataSource() { 

     return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName("product") 
       .addScript("classpath:h2.sql").build(); 
    } 
} 

我有一個測試:

@SpringApplicationConfiguration 
@Transactional 
class MenuRepositoryTest extends Specification { 

    @Shared 
    ConfigurableApplicationContext context 

    @Shared 
    private MenuRepository menuRepository 

    void setupSpec() { 
     Future future = Executors.newSingleThreadExecutor().submit(
       new Callable() { 
        @Override 
        public ConfigurableApplicationContext call() throws Exception { 
         return (ConfigurableApplicationContext) SpringApplication.run(Application.class) 
        } 
       }) 
     context = future.get(60, TimeUnit.SECONDS) 
     menuRepository = context.getBean(MenuRepository.class) 
    } 

    void cleanupSpec() { 
     if (context != null) { 
      context.close() 
     } 
    } 
@Transactional 
    def "test creating a single menu with a single menuItem"() { 

     def menu = new Menu() 
     menu.setDisplayOrder(0) 
     menu.setDisplayText("test") 
     menuRepository.save(menu) 

     def menuItem = new MenuItem() 
     menuItem.setToolTip("tooltip 1") 
     menuItem.setPath("/1") 
     menuItem.setCallType(HttpType.GET) 
     menuItem.setDisplayText("tooltip") 
     menu.addMenuItem(menuItem) 

     when: 
     def menus = menuRepository.findAll() 
     menus[0].getMenuItems() 

     then: 
     menus[0].getMenuItems().size() == 1 

    } 
} 

這裏是我的gradle這個顯示的依賴關係:

apply plugin: 'java' 
apply plugin: 'groovy' 
apply plugin: 'idea' 
apply plugin: 'spring-boot' 
apply plugin: 'jacoco' 
apply plugin: 'war' 
apply plugin: 'maven' 


buildscript { 
    repositories { 
     maven { url "http://repo.spring.io/libs-snapshot" } 
     mavenLocal() 
    } 
    dependencies { 
     classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RC4") 
    } 
} 
repositories { 
    mavenCentral() 
    maven { url "http://repo.spring.io/libs-snapshot" } 
    maven { url 'http://repo.spring.io/milestone' } 
} 

dependencies { 
    compile("org.springframework.boot:spring-boot-starter-web:1.0.0.RELEASE") 
    compile("org.springframework.boot:spring-boot-starter-data-jpa:1.0.1.RELEASE") 
    compile("org.springframework.boot:spring-boot:1.0.1.RELEASE") 
    compile("org.springframework:spring-orm:4.0.0.RC1") 
    compile("org.hibernate:hibernate-entitymanager:4.2.1.Final") 
    compile("org.springframework:spring-tx") 
    compile("com.h2database:h2:1.3.172") 
    compile("joda-time:joda-time:2.3") 
    compile("org.thymeleaf:thymeleaf-spring4") 
    compile("org.codehaus.groovy.modules.http-builder:http-builder:0.7.1") 
    compile('org.codehaus.groovy:groovy-all:2.2.1') 
    compile('org.jadira.usertype:usertype.jodatime:2.0.1') 

    testCompile('org.spockframework:spock-core:0.7-groovy-2.0') { 
     exclude group: 'org.codehaus.groovy', module: 'groovy-all' 
    } 
    testCompile('org.codehaus.groovy.modules.http-builder:http-builder:0.7+') 
    testCompile("junit:junit") 
} 

jacocoTestReport { 
    group = "Reporting" 
    description = "Generate Jacoco coverage reports after running tests." 
} 

sourceSets { 

    main { 

     java { 
      srcDirs = [] 
     } 
     groovy { 
      srcDirs = ['src/main/groovy', 'src/main/java'] 
     } 
     resources { 
      srcDirs = ['src/main/resources'] 
     } 

     output.resourcesDir = "build/classes/main" 
    } 

    test { 
     java { 
      srcDirs = [] 
     } 
     groovy { 
      srcDirs = ['src/test/groovy', 'src/test/java'] 
     } 
     resources { 
      srcDirs = ['src/test/resources'] 
     } 

     output.resourcesDir = "build/classes/test" 
    } 
} 

task wrapper(type: Wrapper) { 
    gradleVersion = '1.11' 
} 

變化build.grad文件來使用不同的斯波克

buildscript { 
    repositories { 
     maven { url "http://repo.spring.io/libs-milestone" } 
     mavenLocal() 
    } 
    dependencies { 
     classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.1.RELEASE") 
    } 
} 
repositories { 
    mavenCentral() 
    maven { url "http://repo.spring.io/libs-milestone" } 
    maven { url "https://repository.jboss.org/nexus/content/repositories/releases" } 
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } 
    maven { url "http://repo.spring.io/snapshot" } 
    maven { url 'http://repo.spring.io/milestone' } 
} 
dependencies { 
compile("org.springframework.boot:spring-boot-starter-web:1.0..RELEASE") 
    compile("org.springframework.boot:spring-boot:1.0.1.RELEASE") 
    compile("org.springframework.boot:spring-boot-starter-data-jpa:1.0.1.RELEASE") 
    testCompile('org.spockframework:spock-core:1.0-groovy-2.0-SNAPSHOT') { 
     exclude group: 'org.codehaus.groovy', module: 'groovy-all' 
    } 

    testCompile('org.spockframework:spock-spring:1.0-groovy-2.0-SNAPSHOT') { 
     exclude group: 'org.spockframework', module: 'spock-core' 
     exclude group: 'org.spockframework', module: 'spring-beans' 
     exclude group: 'org.spockframework', module: 'spring-test' 
     exclude group: 'org.codehaus.groovy', module: 'groovy-all' 
    } 
    testCompile('org.springframework:spring-test:4.0.3.RELEASE') 
...} 

更改測試不使用SetupSpec或@Shared:

@ContextConfiguration(classes = MyApplication, loader = SpringApplicationContextLoader) 
@Transactional 
class MenuRepositoryTest extends Specification { 

    @Autowired 
    private MenuRepository menuRepository 

    def "test creating a single menu with a single menuItem"() { 

     def menu = new Menu() 
     menu.setDisplayOrder(0) 
     menu.setDisplayText("test") 
     menuRepository.save(menu) 

     def menuItem = new MenuItem() 
     menuItem.setToolTip("tooltip 1") 
     menuItem.setPath("/1") 
     menuItem.setCallType(HttpType.GET) 
     menuItem.setDisplayText("tooltip") 
     menu.addMenuItem(menuItem) 

     when: 
     def menus = menuRepository.findAll() 
     menus[0].getMenuItems() 

     then: 
     menus[0].getMenuItems().size() == 1 

    } 
} 
+1

您的映射不正確:雙向關聯必須有一個所有者端和一個由mappedBy屬性標記的反面。在OneToMany中,一方必須是反面。當然,如果你在Menu和MenuItem之間有一個OneToMany,你必須在MenuItem和Menu之間有一個ManyToOne,而不是OneToOne。還要注意targetEntity是無用的:列表是一個'List ',所以Hibernate知道目標實體是MenuItem。 –

+0

我改變了我的映射,但仍然收到相同的錯誤消息。根據我見過的其他例子,這看起來是正確的。這讓我想知道它的交易方式是否在春季啓動時使用,但這是一個瘋狂的猜測。 – sonoerin

+0

Spock可能不知道有關Spring Boot bootstrap功能的任何信息。它是否知道'@ Transactional'?就你的引導使用而言,你所有的'MyConfiguration'都可能被刪除(除了'@ EntitiesS'所在的pacakges可能需要'@ EntityScan')。 Spring Boot已啓用'@ Transactional'併爲您定義事務管理器。 –

回答

3

這個問題的答案問題在於斯波克和Spring集成。這些映射都是正確的,但Spock和Spring並沒有很好地合作。我更新了問題以顯示運行集成測試的正確方法。