Spring基础入门7 - JPA/Hibernate

1. 数据访问层 (DAO模式)

通常我们的应用都要使用数据,涉及到大量的数据存取, 对于企业级应用,数据存放在关系型数据库是最常用的方案。前文提到Spring MVC将前端展现与业务逻辑分离,为了让程序结构更清晰,我们还会将数据访问从业务逻辑中也分离出来,做为一个数据访问层(持久化数据层)。这样我们就获得了一个J2EE经典的三层架构: 表现层 - 业务逻辑层 - 数据访问层。

数据访问层通常使用的DAO模式进行封装,DAO模式主要包括三部分:

  1. DAO接口: 对需要的数据库操作的接口,提供外部(业务逻辑层)使用。这样业务逻辑层与具体的数据访问分离开来。
  2. DAO 实现类: 针对不同数据库编写DAO接口的具体实现。
  3. 实体类: 用于在应用中存放与传递对象数据。类似于MVC中的Model。

我们再看上文的案例数据库,假设一个业务逻辑,需要查询指定用户的账户余额。那我们对数据库访问层的DAO设计如下:
首先我们定义实体类, 我们通常不会直接将JDBC的ResultSet返回给业务层,而是将数据抽象成对象,这里我们需要设计一个Account的实体类,实体类是没有业务逻辑的普通类,只有字段和对应的getter/setter方法。可以理解成C语言中的结构体。

package org.littlestar.learning.package8.entity;

import java.io.Serializable;

public class Account implements Serializable {
	private static final long serialVersionUID = -242844598250198468L;
	private long id;
	private String name;
	private double balance;
	
	public Account() {}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
}

接着我们将数据访问抽象成接口,定义DAO接口:

package org.littlestar.learning.package8;

import org.littlestar.learning.package8.entity.Account;

public interface AccountDao {
	/**
	 * 获取指定账户编号获取账户信息。
	 * @param id 账户编号
	 * @return 指定的账户信息,如果没有找到返回null。
	 */
	Account findAccountById(long id);
}

编写DAO接口的实现类,我们使用上文的JdbcTemplate来实现与数据库交互。

package org.littlestar.learning.package8;

@Repository
public class AccountDaoImpl implements AccountDao {
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@Override
	public Account findAccountById(long id) {
		String sqlText = "select * from learning.account where acct_id = ?";
		RowMapper<Account> rowMapper = new RowMapper<Account>() {
			@Override
			public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
				Account account = new Account();
				account.setId(rs.getLong("acct_id"));
				account.setName(rs.getString("name"));
				account.setBalance(rs.getDouble("balance"));
				return account;
			}
		};
		List<Account> accounts = jdbcTemplate.query(sqlText, rowMapper, id);
		return accounts.isEmpty() ? null : accounts.get(0);
	}
}

这样数据存储的访问层已经编写完成,在业务逻辑中就可以直接使用了:

    ...
	@Autowired
	AccountDao accountDao;
	
	@Test
	public void showAccountBalance() {
		int id = 2;
		Account account = accountDao.findAccountById(id);
		System.out.println("Hello " + account.getName() + ", your balance is: " + account.getBalance());
	}
	...

DAO分离后,业务逻辑简单清晰了很多,没有了Java.sql.*包下了Connection, Statement, ResultSet这些JDBC接口/实现类,实现了业务逻辑与数据库访问的解耦。

2. ORM/JPA

上文AccountDaoImpl中,我们通过Spring-jdbc的RowMapper,将数据库返回的结果集(游标)映射成Java对象。手动编写比较繁琐,有没有方法自动将数据库对象映射成java的对象呢?有的,那就是使用ORM (Object Relational Mapping)框架。Java平台下,当前主流的有: Hibernate和MyBatis。相对于JdbcTemplate,ORM持久化框架对JDBC进行了更高级的封装(隐藏了更多的数据库相关细节),让持久层开发更符合面向对象。开发者通常不需要编写SQL语句,不需要关注数据库是Oracle还是MySQL,底层的SQL语句由框架自动生成。

在学习具体的持久化框架之前,我们先了解一下JPA(Java Persistence API)。JPA是Java对象持久化的API, 是J2EE 5.0的标准规范的一部分,JPA的目的是统一Java应用程序的持久层编程模型。JPA定义了一系列的接口和注解(在javax.persistence.*包下)和一些配置规范(配置文件定义)。JPA没有具体的实现,JPA必须和实现了JPA的框架一起使用。类似于JDBC,如果没有JDBC驱动,光有JDBC的API是无法访问数据库的。

Hibernate是JPA的主要实现之一, Hibernate从3.2开始兼容JPA。你只需要JPA配置文件中provider指定Hibernate,就可以使用JPA实现数据持久化访问。你也可以在Hibernate中使用JPA定义的注解(如@Entity, @Column, @Id, …)来定义对象映射。

在JPA规范中,配置xml配置文件名为persistence.xml,必须放在/META-INF目录下。

<persistence
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
 version="2.1">
    <persistence-unit name="jpa-with-hibernate">
        <description>JPA with Hibernate Example</description>
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>...</class>
        ...
    </persistence-unit>
</persistence>

JPA主要使用EntityManager定义的方法实现数据持久化操作:

	...
	EntityManagerFactory factory = Persistence.createEntityManagerFactory("jpa-with-hibernate");
	EntityManager entityManager = factory .createEntityManager();
	entityManager.XXX();
	...

3. Hibernate

Hibernate是一个基于JDBC的开源的持久化框架,遵循LGPL V2.1开源许可协议, 是一个优秀的全自动ORM框架。在SpringBoot中,JPA的默认实现使用的就是Hibernate。下面学习Spring中使用Hibernate。在开始前需要先引入相关的依赖:

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>5.6.5.Final</version>
		</dependency>
		<!-- spring-orm包含了Spring封装的一些Hiberate工具类, 如HibernateTemplate -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>5.3.15</version>
		</dependency>

3.1. Spring使用xml配置文件集成Hibernate

配置Hibernate相关bean,并装载入Spring容器:

<?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:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

	<context:annotation-config />
	<context:component-scan base-package="org.littlestar.learning.package8" />
	<!-- Hibernate 依赖JDBC -->
	<context:property-placeholder location="classpath:org/littlestar/learning/package8/datasource.properties" />
	<bean id="hikariDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
		<property name="driverClassName" value="${spring.jdbc.driverClassName}" />
		<property name="jdbcUrl" value="${spring.jdbc.url}" />
		<property name="username" value="${spring.jdbc.username}" />
		<property name="password" value="${spring.jdbc.password}" />
		<property name="connectionTestQuery" value="${spring.jdbc.connectionTestQuery}" />
		<property name="maximumPoolSize" value="${spring.jdbc.maxPoolSize}" />
	</bean>

	<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
		<property name="dataSource" ref="hikariDataSource" />
		<property name="packagesToScan" value="org.littlestar.learning.package8.entity" />
		<property name="mappingLocations">
			<list> <!-- Hibernate O/R对象关系映射配置文件位置 -->
				 <value>classpath:org/littlestar/learning/package8/account.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop> <!-- MySQL数据库方言 -->
				<prop key="hibernate.show_sql">true</prop>   <!-- 日志输出显示sql -->
				<prop key="hibernate.format_sql">true</prop> <!-- 日志输出中格式化sql -->
				<prop key="hibernate.generate_statistics">true</prop>
				<prop key="hibernate.autoReconnect">true</prop>
			</props>
		</property>
	</bean>
	
	<!--  bean class="org.littlestar.learning.package8.AccountDaoImpl" /-->
	<!--  HibernateTemplate -->
	<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean> 
	<!-- 配置事务管器,管理持久化过程中的事务 -->
	<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	<!-- 将@Transactional注解的方法自动加入事务管理器 -->
  	<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

接下来编写Hibernate的O/R对象关系配置文件: account.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
        
<hibernate-mapping schema="learning">
	<class name="org.littlestar.learning.package8.entity.Account" table="account">
		<id name="id" column="acct_id" type="java.lang.Long">
			<generator class="native" />
		</id>
		<property name="name" column="name" type="java.lang.String" />
		<property name="balance" column="balance" />
	</class>
</hibernate-mapping>

配置完成后,我们就可以使用Hibernate实现DAO。我们需要的是org.hibernate.Session, Session由SessionFactory创建,SessionFactory由Spring容器做为一个bean装载(通过org.springframework.orm.hibernate5.LocalSessionFactoryBean), 我们只需要@Autowired SessionFactory就可以获取其实例。然后通过SessionFactory的openSession或者getCurrentSession方法即可拿到session。两种方法获取方式有不同:

  • openSession :每次都会打开一个新的session,用完之后需手动关闭session。
  • getCurrentSession:重用同一个session,不需要手动关闭,由Hibernate管理线程安全和事务,性能上更优,但使用上也有限制。

获得Session实例后,就可以调用session的方法实现我们需要的持久化操作,Hibernate支持一种类似于SQL的查询语句, HQL(Hibernate Query Language),HQL是面向对象(实体类)的,而SQL是针对数据库表的。Hibernate也支持使用原生SQL语句,因为SQL语句是硬编码,不如HQL兼容性好(可兼容不同的数据库)。
类似于JdbcTemplate , Spring也提供了封装Hibernate的API的工具类HibernateTemplate,我们也可以直接使用HibernateTemplate简化编码。

package org.littlestar.learning.package8;

import java.util.List;

import javax.persistence.TypedQuery;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.littlestar.learning.package8.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Component // 必须是一个Bean, 才能让Spring容器自动装载(@Autowired) SessionFactory
public class AccountDaoImpl implements AccountDao {
	@Autowired
	private SessionFactory sessionFactory;
	
	//HibernateTemplate: Helper class that simplifies Hibernate data access code. 
	@Autowired
	private HibernateTemplate hibernateTemplate;
	
	/*
	获取所有Account数据, 对应SQL语句:
    select
        account0_.acct_id as acct_id1_0_,
        account0_.name as name2_0_,
        account0_.balance as balance3_0_ 
    from
        account account0_
	*/
	@Override
	@Transactional(  // findAll()方法的事务相关设置
			value = "transactionManager", 
			rollbackFor = Exception.class, 
			isolation = Isolation.READ_COMMITTED,
			propagation = Propagation.REQUIRED,
			readOnly = true)
	public List<Account> findAll() {
		/*
		// 使用HQL实现.
		String hql = "from Account"; // 在HQL中使用的是对象名, 必须与实体类的类名一致, 且严格区分大小写。
		Session session = sessionFactory.getCurrentSession();
		@SuppressWarnings("unchecked")
		TypedQuery<Account> query = session.createQuery(hql);
		List<Account> accounts = query.getResultList();
		return accounts;
		*/
		// 使用HibernateTemplate实现
		return hibernateTemplate.loadAll(Account.class);
	}
	
	/*
	根据主键查询, 对应SQL语句: 
    select
        account0_.acct_id as acct_id1_0_,
        account0_.name as name2_0_,
        account0_.balance as balance3_0_ 
    from
        account account0_ 
    where
        account0_.acct_id=?
	
	*/
	@Override
	@Transactional(readOnly = true)
	public Account findAccountById(long id) {
		// 使用HQL实现
		Session session = sessionFactory.getCurrentSession();
		String hql = "from Account as a WHERE a.id=:id";
		@SuppressWarnings("unchecked")
		TypedQuery<Account> query = session.createQuery(hql).setParameter("id", id);
		List<Account> accounts = query.getResultList();
		
		/*
		// 使用原生SQL实现
		Session session = sessionFactory.openSession();
		String sql = "select * from account where acct_id = ? ";
		session.getTransaction().begin();
		@SuppressWarnings("unchecked")
		List<Account> accounts = session.createNativeQuery(sql)
				.addEntity(Account.class)
				.setParameter(1, id)
				.list();
		session.getTransaction().commit();
		session.close();
		*/
		return accounts.isEmpty() ? null : accounts.get(0);
		
		/*
		// 使用HibernateTemplate实现
		Account account = hibernateTemplate.get(Account.class, id); 
		return account;
		*/
	}
	
	/*
	多条件查询案例, 对应SQL语句: 
	    select
	        this_.acct_id as acct_id1_0_0_,
	        this_.name as name2_0_0_,
	        this_.balance as balance3_0_0_ 
	    from
	        account this_ 
	    where
	        (
	            this_.name like ? 
	            and this_.balance>=?
	        )
	*/  
	@Override
	public List<Account> findByNameAndBlance(String name, double geBalance) {
		DetachedCriteria criteria = DetachedCriteria.forClass(Account.class);
		criteria.add(Restrictions.and(
				Restrictions.like("name", name), 
				Restrictions.ge("balance", geBalance)
			));
		
		// HibernateTemplate的findXXX可以通过参数控制返回数据的位置和数量(类似于MySQL的limit):
		// HibernateTemplate调用的SQL是与不带参数的是一样的, 并非数据库级别的过滤。
		// 从第一条记录开始, 最多返回2调记录, 
		int firstResult = 0,  maxResults = 2; 
		@SuppressWarnings("unchecked")
		List<Account> accounts = (List<Account>) hibernateTemplate.findByCriteria(criteria, firstResult, maxResults);
		// List<Account> accounts = (List<Account>) hibernateTemplate.findByCriteria(criteria);
		return accounts;
	}
	
	/*
	插入Account数据, SQL:
	insert 
	into
	    account
	    (name, balance) 
	values
	    (?, ?)
	*/
	@Transactional(readOnly = false)
	@Override
	public Account save(Account account) {
		hibernateTemplate.save(account);
		hibernateTemplate.flush();
		return account;
	}
	
	/*
	更新给定的Account, SQL:
    update
        account 
    set
        name=?,
        balance=? 
    where
        acct_id=?
	*/
	@Transactional(readOnly = false)
	@Override
	public void update(Account account) {
		hibernateTemplate.update(account);
		hibernateTemplate.flush();
	}
	
	/*
    删除给定的Account, SQL:
    delete 
    from
        account 
    where
        acct_id=?
	*/
	@Transactional(readOnly = false)
	@Override
	public void delete(Account account) {
		hibernateTemplate.delete(account);
		hibernateTemplate.flush();
	}
}

Junit测试代码:

package org.littlestar.learning.package8;

import java.util.List;
import java.util.Objects;
import java.util.Random;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.littlestar.learning.package8.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) 
@ContextConfiguration(locations={"classpath:org/littlestar/learning/package8/package8-bean-config.xml"})
//@ContextConfiguration(classes = HibernateConfig.class)
public class SpringTestPackage8 {
	@Autowired
	AccountDao accountDao;
	
	@Test
	public void testFindAll() {
		List<Account> accounts = accountDao.findAll();
		for (Account account : accounts) {
			System.out.println(account.toString());
		}
	}
	
	@Test
	public void testFindAccountById() {
		Account a = accountDao.findAccountById(3);
		if (Objects.isNull(a)) {
			System.out.println("no found~");
		} else {
			System.out.println(a.toString());
		}
	}
	
	@Test
	public void testFindByNameAndBlance() {
		List<Account> accounts = accountDao.findByNameAndBlance("Tuzki", 1000.0D);
		for (Account account : accounts) {
			System.out.println(account.toString());
		}
	}
	
	public static Account newAccount() {
		Account newAcct = new Account();
		//newAcct.setId(1L);
		newAcct.setName("Tuzki");
		newAcct.setBalance(1000.0D);
		return newAcct;
	}
	
	@Test
	public void testSave() {
		Account newAcct = newAccount();
		Account savedAcct = accountDao.save(newAcct);
		System.out.println("New account saved, id=" + savedAcct.getId());
	}
	
	@Test 
	public void testUpdate() {
		Account acct = accountDao.findAccountById(3);
		acct.setName("newName"+new Random().nextInt(100));
		accountDao.update(acct);
	}
	
	@Test 
	public void testDelete() {
		Account newAcct = newAccount();
		newAcct = accountDao.save(newAcct);
		System.out.println("delete account (" + newAcct.getId()+")");
		accountDao.delete(newAcct);
	}
}

3.2. Spring使用配置类和注解集成Hibernate

使用配置文件来进行O/R对象关系映射太麻烦,且不直观,Hibernate是支持使用注解来定义映射的,Spring的容器也是支持配置类进行配置的。下面是与配置文件等效的配置方法。

package org.littlestar.learning.package8;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.cfg.Environment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
@EnableTransactionManagement
@ComponentScan("org.littlestar.learning.package8")
@PropertySource({ "classpath:org/littlestar/learning/package8/datasource.properties" })
public class HibernateConfig {
	@Value("${spring.jdbc.driverClassName}")
	private String driverClassName;

	@Value("${spring.jdbc.url}")
	private String url;

	@Value("${spring.jdbc.username}")
	private String username;

	@Value("${spring.jdbc.password}")
	private String password;

	@Value("${spring.jdbc.connectionTestQuery}")
	private String connectionTestQuery;

	@Value("${spring.jdbc.maxPoolSize}")
	private int maxPoolSize;

	@Bean(name = "hikariDataSource", destroyMethod = "close")
	public DataSource hikariDataSource() {
		HikariDataSource dataSource = new HikariDataSource();
		dataSource.setDriverClassName(driverClassName);
		dataSource.setJdbcUrl(url);
		dataSource.setUsername(username);
		dataSource.setPassword(password);
		dataSource.setConnectionTestQuery(connectionTestQuery);
		dataSource.setMaximumPoolSize(maxPoolSize);
		return dataSource;
	}

	@Autowired
	@Qualifier("hikariDataSource")
	DataSource hikariDataSource;
	
	@Bean
	public LocalSessionFactoryBean sessionFactory() {
		LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
		sessionFactory.setDataSource(hikariDataSource);
		//使用注解进行O/R映射
		sessionFactory.setPackagesToScan("org.littlestar.learning.package8.entity"); // 扫描实体类(@Entity)的包位置
		//使用传统的XML文件进行O/R映射
		//sessionFactory.setMappingLocations(new ClassPathResource("org/littlestar/learning/package8/account.hbm.xml"));  
		Properties hibernateProperties = hibernateProperties();
		sessionFactory.setHibernateProperties(hibernateProperties);
		return sessionFactory;
	}

	private Properties hibernateProperties() {
		Properties hibernateProperties = new Properties();
		hibernateProperties.setProperty(Environment.FORMAT_SQL, "true");
		hibernateProperties.setProperty(Environment.SHOW_SQL, "true");
		hibernateProperties.setProperty(Environment.DIALECT, "org.hibernate.dialect.MySQL8Dialect");
		hibernateProperties.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "org.springframework.orm.hibernate5.SpringSessionContext");
		return hibernateProperties;
	}
	
	@Autowired
	private LocalSessionFactoryBean sessionFactory;
	@Bean
	public HibernateTemplate hibernateTemplate() {
		HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory.getObject());
		return hibernateTemplate;
	}
	
    @Bean(name = "transactionManager")
    public HibernateTransactionManager hibernateTransactionManager() {
        return new HibernateTransactionManager(sessionFactory.getObject());
    }
}

关系映射通过JPA注解,定义在实体类中。

package org.littlestar.learning.package8.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.google.gson.Gson;

@Entity
@Table(name = "account")
public class Account implements Serializable {
	private static final long serialVersionUID = -242844598250198468L;
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY) 
	@Column(name = "acct_id", nullable = false)  
	private long id;
	
	@Column(name="name", length=16)
	private String name;
	
	@Column(name="balance",length=16)
	private double balance;

	public Account() {}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}
	
	@Override
	public String toString() {
		Gson gson = new Gson();
		return gson.toJson(this);
	}
}

Hibernate对象之间关联

对于关系型数据库,在数据库设计上通常会遵循数据库范式的规范,这意味这我们通常需要进行多表的关联查询,参照关系型数据库,Hibernate也支持4种关联映射: 一对一(@OneToOne), 一对多(@OneToMany), 多对一(@ManyToOne)和多对多(@ManyToMany)。
简单学习部分用法, 数据库新建一张表location,并为account表添加一个location_id字段与之关联:

location_id|city     |
-----------+---------+
          0|Beijing  |
          1|Shanghai |
          2|Guangzhou|
          3|Shenzhen |

对于account的每条记录对应location表的一条记录,所以是一对一关系:

@Entity
@Table(name = "account")
public class Account implements Serializable {
	...
	@Column(name="location_id", nullable = true)
	private int locationId;
	
	@OneToOne
	@JoinColumn(name = "location_id", referencedColumnName = "location_id", nullable = true, insertable = false, updatable = false)
	private Location location;
	//getter, setter
}

对于location的每一条记录,对应accounts有多条记录, 所以是一对多关系:

@Entity
@Table(name = "location")
public class Location {
	@Id
	@Column(name = "location_id", nullable = false)  
	private int locationId;
	
	@Column(name="city")
	private String city;
	
	@OneToMany(fetch=FetchType.EAGER)
	@JoinColumn(name="location_id")
	private Set<Account> accounts; 
	// getter, setter
}

编写测试代码使用:

...
@RunWith(SpringRunner.class) 
//@ContextConfiguration(locations={"classpath:org/littlestar/learning/package8/package8-bean-config.xml"})
@ContextConfiguration(classes = HibernateConfig.class)
public class SpringTestPackage8 {
	@Autowired private SessionFactory sessionFactory;
	@Autowired private HibernateTemplate hibernateTemplate;
	
	@Test
	@Transactional
	public void testOneToOne() {
		long accountId = 3L;
		/* 使用HQL的join实现
		Session session = sessionFactory.getCurrentSession();
		String hql = "from Account as acct inner join acct.location as location WHERE acct.id=:id";
		List<?> list = session.createQuery(hql).setParameter("id", accountId).list();
		for(int i=0; i<list.size(); i++) {
			Object[] row = (Object[]) list.get(i);
			Account a = (Account) row[0];
			//Location l = (Location) row[1];
			System.out.println("Account: " +a.getName()+", Location: " + a.getLocation().getCity());
		}
		*/
		Account account = hibernateTemplate.get(Account.class, accountId);
		System.out.println("Account: " +account.getName()+", Location: "+account.getLocation().getCity());
	}
	
	@Test
	@Transactional
	public void testOneToMany() {
		int locationId = 0;
		/* 使用HQL的join实现
		Session session = sessionFactory.getCurrentSession();
		String hql = "from Location as loc inner join loc.accounts as accts WHERE loc.locationId=:id";
		List<?> list = session.createQuery(hql).setParameter("id", locationId).list();
		for(int i=0; i<list.size(); i++) {
			Object[] row = (Object[]) list.get(i);
			Location l = (Location) row[0];
			Account account = (Account) row[1];
			System.out.println("Account: " +account.getName()+", Location: "+l.getCity());
		}
		*/
		Location location = accountDao.findLocationById(locationId);
		for(Account account: location.getAccounts()) {
			System.out.println("Account: " +account.getName()+", Location: " + account.getLocation().getCity());
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值