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

标签 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) 方法,并使用规范标准来构建查询。类似这样的事情:

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 表示法的情况下使其变得简单吗?

最佳答案

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

注意:

  1. 我正在使用 Spring + Plain JPA 和 Hibernate 实现POC
  2. 我正在使用 Spring 推荐的方式来管理事务。
  3. com.mycompany.h2.jpa 包包含实体类。
  4. 查看一下 mytable.sql,它的结构与您的需求类似。

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');

关于java - JPA:没有 @OneToMany 注释的左连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38307395/

相关文章:

java - 通过关键字将一个大的xml文件拆分成多个小文件

java - oracle中如何向已有的自增表插入一行?

java - 当列表为 null 或为空时,JPA createQuery 将列表作为参数传递

java - 通过提取两个字符行之间的行来从文本文件创建文本文件

java - 如何以编程方式从spring应用程序获取.key文件的路径?

spring - 使用 Spring MVC 和 Hibernate 实现分页

java - 如何防止 EclipseLink (JPA 2.0) 清除我的数据库/表?

java - 如何制作通用的jpa存储库?我应该这样做吗?为什么?

java - 等待 selenium webdriver 直到加载完成

mysql - Hibernate相互一对一关系