JPA:没有@OneToMany注释的左连接

时间:2016-07-11 12:49:41

标签: java hibernate jpa criteria specifications

我的数据库中有一个OneToMany关系,但我不希望Hibernate直接管理它。

这种关系是翻译,但DTO代表自己是翻译的注册表:

@Entity
@Table(name = "my_table")
public class MyTable {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    private Integer id;

    @Transient
    private String lang;

    @Transient
    private String text;

    // getters and setters
    ...
}

@Entity
@Table(name = "my_table_translation")
public class MyTableTranslation {

    @Id
    @Column(name = "id", nullable = false, unique = false)
    private Integer id;

    @Id
    @Column(name = "lang", nullable = false, unique = false, length = 2)
    private String lang;

    @Column(name = "text", nullable = false, unique = false, length = 200)
    private String text;

    // getters and setters
    ...
}

我希望有一个带有lang参数的特定findAll(String lang)方法,并使用Specification Criteria来构建查询。这样的事情:

public void findAll(String language) {
    List<MyTable> list = repository.findAll(new Specification<MyTable>() {
        @Override
        public Predicate toPredicate(Root<MyTable> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            // something there
            return ...;
        }
    });
}

事实是我不知道该怎么做,因为我不能使用JOIN子句,因为我在模型中没有代表关系的属性。

我尝试使用带有SQL表示法的SELECT ... FROM ... LEFT JOIN查询,

SELECT t1, t2 FROM MyTable t1 LEFT JOIN MyTableTranslation t2 ON t1.id = t2.id

它有效,但不是很理想。生成的对象列表是每个项目2个对象的列表:一个是 MyTable 对象,另一个是 MyTableTranslation 相关对象。我需要解析列表并使用Apache Commons库中的PropertyUtils类以编程方式构建对象。

我认为这不干净...有没有人知道如何轻松实现,而不使用SQL表示法?

2 个答案:

答案 0 :(得分:0)

Marc,您可以执行以下操作使其正常工作,而您现在不需要复杂的join子句或谓词。嵌入式H2数据库和JUnit测试中的简单实现足以证明概念( POC ),如下所示

注意:

  1. 我正在使用Spring + Plain JPA with Hibernate实施 POC
  2. 我使用的是Spring推荐的管理交易方式。
  3. com.mycompany.h2.jpa包中包含实体类。
  4. 查看 mytable.sql ,其结构与您的需求类似。
  5. <强> MyTable.java

    @Entity
    @Table(name = "my_table")
    public class MyTable implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name = "id", nullable = false, unique = true)
        @JoinColumn(referencedColumnName="id", insertable=true, updatable=false)
        private Long id;
    
        @Column(name = "lang", unique=true)
        private String lang;
    
        @Column(name = "text")
        private String text;    
    
        @OneToMany(fetch = FetchType.LAZY)
        @JoinColumn(name="id", insertable=true, updatable=true, referencedColumnName="id")
        private List<MyTableTranslation> translations = new ArrayList<MyTableTranslation>();
        ...
        // getters and setters, toString()
    }   
    

    <强> MyTableTranslation.java

    @Entity
    @Table(name = "my_table_translation")
    public class MyTableTranslation implements Serializable {
    
        private static final long serialVersionUID = 11L;
    
        @Id
        @Column(name = "id")
        private Long id;
    
        @Id
        @Column(name = "speaker")
        String speaker;
        ...
        // getters and setters, toString()
    }
    

    <强> TestH2DatabaseConfiguration.java

    @Configuration
    @EnableTransactionManagement
    public class TestH2DatabaseConfiguration {
    
        final static Logger log = LoggerFactory.getLogger(TestH2DatabaseConfiguration.class);
    
        @Bean
        @Qualifier("dataSource")
        public DataSource h2DataSource() {
            return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).addScript("classpath:mytable.sql").build();
        }
    
        @Bean
        public EntityManagerFactory entityManagerFactory() {
    
            HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
            jpaVendorAdapter.setGenerateDdl(true);
    
            LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
    
            factoryBean.setDataSource(h2DataSource());
            factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
            factoryBean.setPackagesToScan("com.mycompany.h2.jpa");
            factoryBean.setPersistenceUnitName("my_table");
    
            Properties prop = new Properties();
            prop.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
            prop.put("hibernate.show_sql", "true");
            prop.put("hibernate.hbm2ddl.auto", "none");
            factoryBean.setJpaProperties(prop);
    
            factoryBean.afterPropertiesSet();
    
            return factoryBean.getObject();
        }
    
        @Bean
        public PlatformTransactionManager transactionManager() {
            JpaTransactionManager txManager = new JpaTransactionManager();
            txManager.setEntityManagerFactory(entityManagerFactory());
            return txManager;
        }
    
        @Bean
        public MyTableDAO myTableDAO() {
            return new MyTableDAOJPAImpl();
        }
    
        @Bean
        public MyTableServiceImpl myTableService() {
            MyTableServiceImpl myTableService = new MyTableServiceImpl();
            myTableService.setMyTableDAO(myTableDAO());
            return myTableService;
        }
    }
    

    <强> MyTableService.java

    public interface MyTableService {   
        public MyTable saveMyTableTranslation(MyTable myTable);
        public List<MyTable> getAllMyTables();
        public MyTable getMyTable(Long entityId);
        public MyTable getMyTable(String lang);
    }
    

    <强> MyTableServiceImpl.java

    @Transactional
    public class MyTableServiceImpl implements MyTableService {
    
        final static Logger log = LoggerFactory.getLogger(MyTableServiceImpl.class);
    
        private MyTableDAO myTableDAO;
    
        public void setMyTableDAO(MyTableDAO myTableDAO) {
            this.myTableDAO = myTableDAO;
        }   
    
        public MyTable saveMyTableTranslation(MyTable myTable) {
            return myTableDAO.saveMyTableTranslation(myTable);
        }
    
        public List<MyTable> getAllMyTables() {
            return myTableDAO.getAllMyTables();
        }
    
        public MyTable getMyTable(Long entityId) {
            return myTableDAO.getMyTable(entityId);
        }
    
        public MyTable getMyTable(String lang) {
            return myTableDAO.getMyTable(lang);
        }
    }
    

    <强> MyTableDAO.java

    public interface MyTableDAO {
        public MyTable saveMyTableTranslation(MyTable myTable);
        public List<MyTable> getAllMyTables();
        public MyTable getMyTable(Long entityId);
        public MyTable getMyTable(String lang);
    }
    

    <强> MyTableDAOJPAImpl.java

    public class MyTableDAOJPAImpl implements MyTableDAO {
    
        final static Logger log = LoggerFactory.getLogger(MyTableDAOJPAImpl.class);
    
        @PersistenceContext
        private EntityManager entityManager;
    
        public MyTable saveMyTableTranslation(MyTable myTable) {
            entityManager.persist(myTable);
            return myTable;
        }
    
        @SuppressWarnings("unchecked")
        public List<MyTable> getAllMyTables() {
            return (List<MyTable>) entityManager.createQuery("FROM MyTable").getResultList();       
        }
    
        public MyTable getMyTable(Long entityId) {
            return (MyTable) entityManager.createQuery("FROM MyTable m WHERE m.id = :id ").setParameter("id", entityId).getSingleResult();
        }
    
        public MyTable getMyTable(String lang) {
            return (MyTable) entityManager.createQuery("FROM MyTable m WHERE m.lang = :lang ").setParameter("lang", lang).getSingleResult();
        }
    }
    

    MyTableTest.java (一个JUnit测试类)

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = { TestH2DatabaseConfiguration.class }, loader = AnnotationConfigContextLoader.class)
    public class MyTableTest extends AbstractTransactionalJUnit4SpringContextTests {
    
        final static Logger log = LoggerFactory.getLogger(MyTableTest.class);
    
        @Autowired
        @Qualifier("myTableService")
        MyTableService myTableService;  
    
    
        @Test
        public void test() throws ParseException {
    
            MyTable parent = new MyTable();
            parent.setLang("Italian");
            parent.setText("Fast...");
    
            MyTableTranslation child = new MyTableTranslation();
            child.setSpeaker("Liotta");
    
            parent = myTableService.saveMyTableTranslation(parent);
    
            log.debug("parent ID : " + parent.getId());
    
            MyTable spanishTables= myTableService.getMyTable("Spanish");
            List<MyTableTranslation> spanishTranslations = spanishTables.getTranslations();
            log.debug("spanishTranslations SIZE : " + spanishTranslations.size());
    
            for (MyTableTranslation myTableTranslation : spanishTranslations) {
                log.debug("myTableTranslation -> : " + myTableTranslation);
            }       
        }
    }
    

    <强> mytable.sql

    CREATE TABLE IF NOT EXISTS my_table (
      id      IDENTITY PRIMARY KEY,
      lang    VARCHAR UNIQUE,
      text    VARCHAR
    );
    
    delete from my_table;
    INSERT INTO my_table VALUES (1, 'Spanish', 'Beautiful...');
    INSERT INTO my_table VALUES (2, 'English', 'Great...');
    INSERT INTO my_table VALUES (3, 'French', 'Romantic...');
    
    
    CREATE TABLE IF NOT EXISTS my_table_translation (
      id      INTEGER,
      speaker VARCHAR
    );
    
    delete from my_table_translation;
    INSERT INTO my_table_translation VALUES (1, 'Eduardo');
    INSERT INTO my_table_translation VALUES (1, 'Diego');
    INSERT INTO my_table_translation VALUES (2, 'George');
    INSERT INTO my_table_translation VALUES (3, 'Pierre');
    

答案 1 :(得分:-1)

我尝试使用@OneToMany注释,然后更改了我的DTO:

@Entity
@Table(name = "my_table")
public class MyTable {

    @Id
    @Column(name = "id", nullable = false, unique = true)
    private Integer id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "id", referencedColumnName = "id")
    private List<MyTableTranslation> translations = new ArrayList<MyTableTranslation>();

    // getters and setters
    ...
}

并将标准更改为:

public void findAll(String isolang) {
    List<MyTable> list = repository.findAll(new Specification<MyTable>() {
        @Override
        public Predicate toPredicate(Root<MyTable> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Join<Object, Object> langJoin = root.join("translations", JoinType.LEFT);
            return cb.equal(langJoin.get("lang"), isolang);
        }
    });
}

但是这个清单没有任何物品。如果我将 FetchType 更改为 EAGER ,则谓词无效,我会获得所有语言。我现在不知道该怎么办......