栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring基础入门7 - JPA/Hibernate

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring基础入门7 - JPA/Hibernate

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

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

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

    DAO接口: 对需要的数据库操作的接口,提供外部(业务逻辑层)使用。这样业务逻辑层与具体的数据访问分离开来。DAO 实现类: 针对不同数据库编写DAO接口的具体实现。实体类: 用于在应用中存放与传递对象数据。类似于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 {
	
	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 rowMapper = new RowMapper() {
			@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 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目录下。


    
        JPA with Hibernate Example
        org.hibernate.jpa.HibernatePersistenceProvider
        ...
        ...
    

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。在开始前需要先引入相关的依赖:

		
			org.hibernate
			hibernate-core
			5.6.5.Final
		
		
		
			org.springframework
			spring-orm
			5.3.15
		
3.1. Spring使用xml配置文件集成Hibernate

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




	
	
	
	
	
		
		
		
		
		
		
	

	
		
		
		
			 
				 classpath:org/littlestar/learning/package8/account.hbm.xml
			
		
		
			
				org.hibernate.dialect.MySQL8Dialect 
				true   
				true 
				true
				true
			
		
	
	
	
	
	
		
	 
	
	
		
	
	
  	

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



        

	
		
			
		
		
		
	

配置完成后,我们就可以使用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;
	
	
	@Override
	@Transactional(  // findAll()方法的事务相关设置
			value = "transactionManager", 
			rollbackFor = Exception.class, 
			isolation = Isolation.READ_COMMITTED,
			propagation = Propagation.REQUIRED,
			readonly = true)
	public List findAll() {
		
		// 使用HibernateTemplate实现
		return hibernateTemplate.loadAll(Account.class);
	}
	
	
	@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 query = session.createQuery(hql).setParameter("id", id);
		List accounts = query.getResultList();
		
		
		return accounts.isEmpty() ? null : accounts.get(0);
		
		
	}
	
	  
	@Override
	public List 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 accounts = (List) hibernateTemplate.findByCriteria(criteria, firstResult, maxResults);
		// List accounts = (List) hibernateTemplate.findByCriteria(criteria);
		return accounts;
	}
	
	
	@Transactional(readonly = false)
	@Override
	public Account save(Account account) {
		hibernateTemplate.save(account);
		hibernateTemplate.flush();
		return account;
	}
	
	
	@Transactional(readonly = false)
	@Override
	public void update(Account account) {
		hibernateTemplate.update(account);
		hibernateTemplate.flush();
	}
	
	
	@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 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 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 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;
		
		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;
		
		Location location = accountDao.findLocationById(locationId);
		for(Account account: location.getAccounts()) {
			System.out.println("Account: " +account.getName()+", Location: " + account.getLocation().getCity());
		}
	}
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/732142.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号