欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

Spring基础入门7

时间:2023-07-06
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;@Repositorypublic class AccountDaoImpl implements AccountDao {@AutowiredJdbcTemplate jdbcTemplate;@Overridepublic Account findAccountById(long id) {String sqlText = "select * from learning.account where acct_id = ?";RowMapper rowMapper = new RowMapper() {@Overridepublic 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);}}

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

...@AutowiredAccountDao accountDao;@Testpublic 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.hibernatehibernate-core5.6.5.Finalorg.springframeworkspring-orm5.3.15

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

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

<?xml version="1.0" encoding="UTF-8"?> classpath:org/littlestar/learning/package8/account.hbm.xmlorg.hibernate.dialect.MySQL8Dialect true true true true

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

<?xml version="1.0" encoding="UTF-8"?>

配置完成后,我们就可以使用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) SessionFactorypublic class AccountDaoImpl implements AccountDao {@Autowiredprivate SessionFactory sessionFactory;//HibernateTemplate: Helper class that simplifies Hibernate data access code、@Autowiredprivate 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);} @Overridepublic 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)@Overridepublic Account save(Account account) {hibernateTemplate.save(account);hibernateTemplate.flush();return account;}@Transactional(readonly = false)@Overridepublic void update(Account account) {hibernateTemplate.update(account);hibernateTemplate.flush();}@Transactional(readonly = false)@Overridepublic 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 {@AutowiredAccountDao accountDao;@Testpublic void testFindAll() {List accounts = accountDao.findAll();for (Account account : accounts) {System.out.println(account.toString());}}@Testpublic void testFindAccountById() {Account a = accountDao.findAccountById(3);if (Objects.isNull(a)) {System.out.println("no found~");} else {System.out.println(a.toString());}}@Testpublic 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;}@Testpublic 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;@Beanpublic 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;}@Autowiredprivate LocalSessionFactoryBean sessionFactory;@Beanpublic 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;}@Overridepublic 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@Transactionalpublic void testOneToOne() {long accountId = 3L;Account account = hibernateTemplate.get(Account.class, accountId);System.out.println("Account: " +account.getName()+", Location: "+account.getLocation().getCity());}@Test@Transactionalpublic 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());}}}

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。