18.1 创建数据库表
Mysql:-- Create tablecreate table BANK_ACCOUNT( ID BIGINT not null, NAME VARCHAR(128) not null, BALANCE DOUBLE not null) ;-- alter table BANK_ACCOUNTadd constraint BANK_ACCOUNT_PK primary key (ID); Insert into Bank_Account(ID, Name, Balance) values (1, 'zhangsan', 1000);Insert into Bank_Account(ID, Name, Balance) values (2, 'lisi', 2000);Insert into Bank_Account(ID, Name, Balance) values (3, 'wangwu', 3000);
18.2 创建Spring Boot项目
提示:Spring Data JPA中已经包含了Hibernate
18.3 完整的pom.xml
<?xml version="1.0" encoding="UTF-8"?>4.0.0org.springframework.bootspring-boot-starter-parent2.2.11.RELEASEme.laocatSpringBootHibernate1.0.0SpringBootHibernateSpring Boot and Hibernate1.8org.springframework.bootspring-boot-starter-data-jpaorg.springframework.bootspring-boot-starter-thymeleaforg.springframework.bootspring-boot-starter-weborg.threeten threetenbp 1.3.6mysqlmysql-connector-javaruntimeorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-engineorg.springframework.bootspring-boot-maven-plugin
18.4 配置Hibernate
为了使 Spring可以连接到数据库,您需要在application.properties文件中配置必要的参数 。
application.properties
# ===============================# DATABASE# ===============================spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/springboothibernate?serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=root# ===============================# JPA/HIBERNATE# ===============================spring.jpa.show-sql=truespring.jpa.hibernate.ddl-auto=updatespring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialectspring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
注意:默认情况下, Spring Boot将自动配置 JPA,并创建 与JPA相关的 Spring bean 。Spring Boot的这些自动配置 包括:
- 数据源自动配置
- DataSourceTransactionManagerAutoConfiguration
- HibernateJpaAutoConfiguration
该应用程序的目的是使用 Hibernate,因此,我们需要禁用上述Spring Boot的自动配置 。
me.laocat.hibernate.SpringBootHibernateApplication
package me.laocat.hibernate;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;@SpringBootApplication@EnableAutoConfiguration(exclude = { // 排除自动配置项 DataSourceAutoConfiguration.class, // 数据源自动配置 DataSourceTransactionManagerAutoConfiguration.class, // 数据源事务管理自动配置 HibernateJpaAutoConfiguration.class }) // Hibernate自动配置public class SpringBootHibernateApplication {public static void main(String[] args) {SpringApplication.run(SpringBootHibernateApplication.class, args);}}
然后,配置Hibernate所需 的 Spring Bean。
me.laocat.hibernate.SpringBootHibernateApplication
package me.laocat.hibernate;import java.io.IOException;import java.util.Properties;import javax.sql.DataSource;import org.hibernate.SessionFactory;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.core.env.Environment;import org.springframework.jdbc.datasource.DriverManagerDataSource;import org.springframework.orm.hibernate5.HibernateTransactionManager;import org.springframework.orm.hibernate5.LocalSessionFactoryBean;@SpringBootApplication@EnableAutoConfiguration(exclude = { // 排除自动配置项DataSourceAutoConfiguration.class, // 数据源自动配置DataSourceTransactionManagerAutoConfiguration.class, // 数据源事务管理自动配置HibernateJpaAutoConfiguration.class }) // Hibernate自动配置public class SpringBootHibernateApplication {private static final Logger log = LoggerFactory.getLogger(SpringBootHibernateApplication.class);@Autowiredprivate Environment env;public static void main(String[] args) {SpringApplication.run(SpringBootHibernateApplication.class, args);}// 获取数据源@Bean(name = "dataSource")public DataSource getDataSource() {DriverManagerDataSource dataSource = new DriverManagerDataSource();// 参见: application.propertiesdataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));dataSource.setUrl(env.getProperty("spring.datasource.url"));dataSource.setUsername(env.getProperty("spring.datasource.username"));dataSource.setPassword(env.getProperty("spring.datasource.password"));log.info("getDataSource:{}", dataSource);return dataSource;}// 获取Hibernate的SessionFactory@Autowired@Bean(name="sessionFactory")public SessionFactory getSessionFactory(DataSource dataSource) throws IOException {Properties hibernateProperties = new Properties();hibernateProperties.put("hibernate.dialect", env.getProperty("spring.jpa.properties.hibernate.dialect"));hibernateProperties.put("hibernate.show_sql", env.getProperty("spring.jpa.show-sql"));hibernateProperties.put("current_session_context_class",env.getProperty("spring.jpa.properties.hibernate.current_session_context_class"));LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();factoryBean.setPackagesToScan(new String[] {""});factoryBean.setDataSource(dataSource);factoryBean.setHibernateProperties(hibernateProperties);factoryBean.afterPropertiesSet();SessionFactory sessionFactory = factoryBean.getObject();log.info("getSessionFactory:{}",sessionFactory);return sessionFactory;}// 获取Hibernate事务管理器@Autowired@Bean(name="transactionManager")public HibernateTransactionManager getHibernateTransactionManager(SessionFactory sessionFactory) {HibernateTransactionManager transactionManager = new HibernateTransactionManager(sessionFactory);return transactionManager;}}
18.5 Entity, Model, Form, DAO
在 JPA(或 Hibernate)中, Entity 是数据库中表的代表类(对应于)。此类的字段将对应于表的列。
我们将创建一个 BankAccount 类来代表 数据库中的 BANK_ACCOUNT。 JPA批注将用于对字段进行批注,以描述字段和表的列之间的映射。这些映射是1-1。每个字段对应于表的1列。
me.laocat.hibernate.entity.BankAccount
package me.laocat.hibernate.entity;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.Table;/** * 描述: 银行账户 * @author 技术猫 */@Entity@Table(name = "Bank_Account")public class BankAccount {@Id @GeneratedValue @Column(name = "id", nullable = false) private Long id;@Column(name = "name", length = 128, nullable = false)private String name;@Column(name = "Balance", nullable = false) private double balance;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;}}
Entity表示表的一条记录的数据,而Model表示查询语句的一条记录的数据(从一个或多个表连接)。当您对一个或多个表的某些列感兴趣时,您可以使用Model类。
Model类:
package me.laocat.hibernate.model;/** * 描述: 银行账户模型类 * @author laocat */public class BankAccountInfo {private Long id; private String name; private double balance; public BankAccountInfo() {}public BankAccountInfo(Long id, String name, double balance) {super();this.id = id;this.name = name;this.balance = balance;}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;}}
自定义异常类:
package me.laocat.hibernate.exception;/** * 描述:自定义异常类 * @author laocat */public class BankTransactionException extends Exception {private static final long serialVersionUID = 1196803155571800009L;public BankTransactionException(String message) {super(message);}}
BankAccountDAO.java
package me.laocat.hibernate.dao;import java.util.List;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.hibernate.query.Query;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Repository;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import me.laocat.hibernate.entity.BankAccount;import me.laocat.hibernate.exception.BankTransactionException;import me.laocat.hibernate.model.BankAccountInfo;@Repository@Transactionalpublic class BankAccountDAO {@Autowiredprivate SessionFactory sessionFactory;public BankAccountDAO() {}/** * 根据ID查询账户 * @param id * @return */public BankAccount findById(Long id) {Session session = this.sessionFactory.getCurrentSession();return session.get(BankAccount.class, id);}/** * 查询所有账户 * @return */public List listBankAccountInfo() {String sql = "Select new " + BankAccountInfo.class.getName() //+ "(e.id,e.name,e.balance) " //+ " from " + BankAccount.class.getName() + " e ";Session session = this.sessionFactory.getCurrentSession();Query query = session.createQuery(sql, BankAccountInfo.class);return query.getResultList();}/** * 入账 * @param id 账户ID * @param amount 金额 * @throws BankTransactionException */// propagation: 表示传播特性// MANDATORY: 表示支持当前事务,如果事务不存在将报异常,所以必须先创建事务. @Transactional(propagation = Propagation.MANDATORY) public void addAmount(Long id, double amount) throws BankTransactionException { BankAccount account = this.findById(id); if (account == null) { throw new BankTransactionException("账户没找到 " + id); } double newBalance = account.getBalance() + amount; if (account.getBalance() + amount < 0) { throw new BankTransactionException( "账户金额 '" + id + "' 不足 (" + account.getBalance() + ")"); } account.setBalance(newBalance); } /** * 转账 * @param fromAccountId 转出账户 * @param toAccountId 转入账户 * @param amount 金额 * @throws BankTransactionException */ // 不要在此方法中捕获 BankTransactionException. // REQUIRES_NEW: 表示创建一个新事务,如果存在当前事务,则挂起当前事务 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = BankTransactionException.class) public void sendMoney(Long fromAccountId, Long toAccountId, double amount) throws BankTransactionException { addAmount(toAccountId, amount); addAmount(fromAccountId, -amount); }}
SendMoneyForm.java
解释Spring事务的运行机制:
在这个示例中,我模拟了一个银行事务。A账户寄给B账户700元。因此,将在数据库中创建两个动作:
B账户增加700元
从A账户中减去700元。
如果第一次操作成功(给B账户增加700元),但第二次操作因为某种原因而失败,银行将遭受损失。
因此,它需要管理事务,以确保如果操作失败,数据将回滚原始状态(在事务之前)。当所有操作都成功时,事务被认为是成功的。
18.6 Controller
package me.laocat.hibernate.controller;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import me.laocat.hibernate.dao.BankAccountDAO;import me.laocat.hibernate.exception.BankTransactionException;import me.laocat.hibernate.form.SendMoneyForm;import me.laocat.hibernate.model.BankAccountInfo;@Controllerpublic class BanAccountController {@Autowired private BankAccountDAO bankAccountDAO; @RequestMapping(value = "/", method = RequestMethod.GET) public String showBankAccounts(Model model) { List list = bankAccountDAO.listBankAccountInfo(); model.addAttribute("accountInfos", list); return "accountsPage"; } @RequestMapping(value = "/sendMoney", method = RequestMethod.GET) public String viewSendMoneyPage(Model model) { SendMoneyForm form = new SendMoneyForm(1L, 2L, 700d); model.addAttribute("sendMoneyForm", form); return "sendMoneyPage"; } @RequestMapping(value = "/sendMoney", method = RequestMethod.POST) public String processSendMoney(Model model, SendMoneyForm sendMoneyForm) { System.out.println("Send Money::" + sendMoneyForm.getAmount()); try { bankAccountDAO.sendMoney(sendMoneyForm.getFromAccountId(), // sendMoneyForm.getToAccountId(), // sendMoneyForm.getAmount()); } catch (BankTransactionException e) { model.addAttribute("errorMessage", "Error: " + e.getMessage()); return "/sendMoneyPage"; } return "redirect:/"; }}
18.7 Thymeleaf 模板
账户 | 转账
accountsPage.html
银行
所有账户
ID Name Balance .. .. ..
sendMoneyPage.html
银行
转账
1 - zhangsan 2 - lisi 3 - wangwu
..
转出账户ID 转入账户ID 金额
18.8 启动应用
访问:http://localhost:8080/