Spring Data快速入门
Spring Data 提供了一系列的目标数据库的存储解决方案,其中包括但不限于关系型数据库(如 Oracle、MySQL、PostgreSQL、SQL Server 等)、文档数据库(如 MongoDB)、图形数据(如 Neo4j)以及 Redis 等 NoSQL 数据库等,它为这些数据库的访问提供了一致的 API 接口,从而使开发人员能够以相同的方式管理和操作各种数据存储系统。
SpringData统一了不同的数据存储,提升了开发效率,降低了学习成本
SpringData致力为数据访问层(DAO)提供熟悉且一致的基于Spring的编程模板。目的就是统一和简化对不同类型持久性存储(关系型数据库系统和NoSQL数据存储)的访问。
SpringData的主要特性:
mongoTemplate、redisTemplate、jdbcTemplate…
可以通过XML或注解进行对象关系映射
如:
- CrudRepository
- PagingAndSortingRepository
- JpaRepository等
相同处:
不同处:
Sun在JDK1.5提出了JPA,JPA全称Java Persistence API( 2019年重新命名为 Jakarta Persistence API ), 是Sun官方提出的 一种ORM规范。
JPA规范为我们提供了:
- ORM映射元数据:通过XML、注解方式完成对象和表的映射(@Entity、@Table、@Id、@Column等)
- JPA的API:用来操作实体对象,执行CRUD操作
- JPQL查询语言
JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口。
JPA是一种规范(接口),而接口是需要实现才能工作的。Hibernate就是实现了JPA规范的ORM框架。
MyBatis并没有实现JPA规范。
- MyBatis:小巧、方便、高效、简单、半自动、就是对JDBC进行了简单的封装、国内更流行(业务复杂)
- Hibernate:强大、方便、高效、全自动、根据ORM映射生成不同SQL、不方便处理业务复杂的场景、在业务相对简单的系统中进行使用、国外更流行。
官网地址:https://docs.jboss.org/hibernate/orm/5.5/userguide/html_single/Hibernate_User_Guide.html#hql
<!--hibernate-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.32.Final</version>
</dependency>
<!--junit4-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
Consumer:
@Entity
@Table(name = "customer")
@Data
public class Consumer {
/*
@Id:声明主键
@GeneratedValue:配置主键的生成策略
- GenerationType.IDENTITY:自增(MySQL)
- GenerationType.SEQUENCE:序列(Oracle)
- GenerationType.TABLE:JPA提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
- GenerationType.AUTO:由程序自动帮助我们选择主键生成策略
@Column:配置属性和字段的映射关系
name:数据库表中字段的名称
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")//指定数据库对应列名
private Long id;
@Column(name = "name")
private String name;
@Column(name = "address")
private String address;
}
只要我们在配置文件中配置,
,hibernate就会自动给我们创建表
update
DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost:3306/springdata_jpaproperty>
<property name="hibernate.connection.username">rootproperty>
<property name="hibernate.connection.password">200151property>
<property name="connection.driver_class">com.mysql.jdbc.Driverproperty>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialectproperty>
<property name="hibernate.show_sql">trueproperty>
<property name="hibernate.format_sql">trueproperty>
<property name="hbm2ddl.auto">updateproperty>
<mapping class="com.zi.entity.Consumer">mapping>
session-factory>
hibernate-configuration>
TestHibernate.java:
public class TestHibernate {
private SessionFactory sf;
@Before
public void init(){
//读取hibernate配置文件
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure("/hibernate.cfg.xml").build();
//根据服务注册类创建一个元数据资源集,同时构建元数据并生成唯一的session工厂
sf = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}
@Test
public void testCreate(){
//1. 创建session
Session session = sf.openSession();
//2. 开始事务
Transaction tx = session.beginTransaction();
//3. 创建消息实例
Consumer consumer = new Consumer();
consumer.setName("张三");
//4. 保存消息
session.save(consumer);
//5. 提交事务
tx.commit();
//6. 关闭session
session.close();
sf.close();
}
}
结果:
- hibernate中也有延迟加载的方法(即:用到该对象才会去查询)
- 同时hibernate也存在类似于Mybatis的两级缓存
拓展:
如果单独使用hibernate的API来进行持久化操作,无法随意切换其他ORM框架,可以通过如下方式切换:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProviderprovider>
<properties>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="111111"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
properties>
persistence-unit>
persistence>
JPA对象的四种状态:
如果你操作的是数据库中存在的对象(持久状态),就算没有提交事务,执行更新等方法后也依然可以改变对应的属性值
<dependencies>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-jpaartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>5.4.32.Finalversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.29version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.8version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.10version>
<scope>testscope>
dependency>
dependencies>
//CrudRepository : T指定操作的实体对象 ID:指定主键类型
//PagingAndSortingRepository自带分页和排序的Repository
public interface ConsumerRepository extends CrudRepository<Consumer, Long> {
}
@Entity
@Table(name = "customer")
@Data
public class Consumer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")//指定数据库对应列名
private Long id;
@Column(name = "name")
private String name;
@Column(name = "address")
private String address;
}
<?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:jpa="http://www.springframework.org/schema/data/jpa" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--用于整合jpa @EnableJpaRepositories -->
<jpa:repositories base-package="com.zi.repositories"
entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"
/>
<!--EntityManagerFactory-->
<bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<!--Hibernate实现-->
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--生成数据库表-->
<property name="generateDdl" value="true"></property>
<property name="showSql" value="true"></property>
</bean>
</property>
<!--设置实体类的包-->
<property name="packagesToScan" value="com.zi.entity"></property>
<property name="dataSource" ref="dataSource" ></property>
</bean>
<!--数据源-->
<bean class="com.alibaba.druid.pool.DruidDataSource" name="dataSource">
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springdata_jpa?characterEncoding=UTF-8"/>
</bean>
<!--声明式事务-->
<bean class="org.springframework.orm.jpa.JpaTransactionManager" name="transactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!--启动注解方式的声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
测试类中引用配置文件
@ContextConfiguration("/spring.xml")
@Configuration // 标记当前类为配置类 =xml配文件
@EnableJpaRepositories(basePackages="com.zi.repositories") // 启动jpa
@EnableTransactionManagement // 开启事务
public class SpringDataJPAConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/springdata_jpa?characterEncoding=UTF-8");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.zi.entity");
factory.setDataSource(dataSource());
return factory;
}
/*
*
* */
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
测试类中引用配置类
@ContextConfiguration(classes = SpringDataJPAConfig.class)
测试类:
//@ContextConfiguration("/spring.xml")
@ContextConfiguration(classes = SpringDataJPAConfig.class)
@RunWith(SpringRunner.class)
public class SpringDataJPATest {
@Autowired
private ConsumerRepository repository;
@Test
public void testR(){
Optional<Consumer> res = repository.findById(1L);
System.out.println(res);
//repository.delete(customer);
// CrudRepository ,有一个 PagingAndSortingRepository 抽象,它添加了额外的方法来简化对实体的分页访问
}
//测试排序
@Test
public void testSort(){
Sort.TypedSort<Consumer> sortType = Sort.sort(Consumer.class);
//根据id降序排列,如果有多个条件,可以直接在sortType后面.and
Sort sort = sortType.by(Consumer::getId).descending();
Iterable<Consumer> all = repository.findAll(sort);
System.out.println(all);
}
//测试分页
@Test
public void testPage(){
//PageRequest.of(page, size, sort) 起始页,每页大小,排序
Page<Consumer> all = repository.findAll(PageRequest.of(0, 2));
System.out.println(all.getTotalElements());
System.out.println(all.getTotalPages());
System.out.println(all.getContent());
}
}
@Query
- 查询如果返回单个实体就用pojo接收,如果是多个需要使用集合
- 参数设置方式
1. 索引: ?数字
2. 具名: :参数名 结合@Param注解指定参数名字
- 增删改
1. 要加上事务的支持
2. 如果是插入方法,一定只有才hibernate下才支持(伪插入:insert into ...select)
@Transactional //通常会放在逻辑层上声明
@Modifying //通知SpringDataJPA是增删改操作
(通过一定的方法名命名规则自动生成)
推荐安装:JPABuddy插件,它能够帮我们提示
将Repository继承QueryByExampleExecutor
在之前使用Query by Example只能针对字符串进行条件设置,那如果希望对所有类型支持,可以使用Specifications
继承接口JpaSpecificationExecutor
不能分组、聚合函数, 需要自己通过entityManager玩
官网:https://querydsl.com/
- QueryDSL是基于ORM框架或SQL平台上的一个通用查询框架。借助QueryDSL可以在任何支持的ORM框架或SQL平台上以通用API方式构建查询。
- JPA是QueryDSL的主要集成技术,是JPQL和Criteria查询的代替方法。目前QueryDSL支持的平台包括JPA,JDO,SQL,Mongodb 等等。。。
- Querydsl扩展能让我们以链式方式代码编写查询方法。该扩展需要一个接口QueryDslPredicateExecutor,它定义了很多查询方法。
使用过程中需要引入querydsl-jpa
依赖,同时配置插件,让maven构建出对应实体类的Q对象
SpringDataJPA的多表关联使用的是hibernate实现,它并没有提供额外的封装等
用户和实体是一对一的关系,一个账户对应一个用户,一个用户只能有一个账户
Account:
声明为entity,指明和Customer为一对一关系,并且指明外键是和customer_id
@Entity
@Table(name = "tb_account")
@Data
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@OneToOne
@JoinColumn(name = "customer_id")
private Customer customer;
}
Customer:
标明为entity,OneToOne声明customer和account是一对一关系,mappedBy将外键交给customer维护
@Entity
@Table(name = "tb_customer")
@Data
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_address")
private String custAddress;
/*
单向关联:一对一
cascade 设置关联操作
- ALL 所有持久化操作
- PERSIST 只有插入时才会执行关联操作
- MERGE 只有修改时才会执行
- REMOVE 只有执行时
fetch 设置是否懒加载
- EAGER 立即加载(默认)
- LAZY 懒加载(直到用到对象才会进行查询,因为不是所有的关联对象都需要用到)
orphanRemoval 关联移除(通常在修改的时候会用到)
- true:当关联的数据设置为null,或者修改其他的关联数据,就会删除关联数据
optional
- true(默认) 限制关联对象可以为null
- false
mappedBy
- 将外键约束交给另一方维护(通常在双向关联关系中,会放弃一方的外键约束)
值 = 另一方关联属性名
*/
@OneToOne(mappedBy = "customer",
cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
//设置外键的字段名
@JoinColumn(name = "account_id")
private Account account;
}
DAO:
public interface CustomerRepository extends PagingAndSortingRepository<Customer,Long>{
}
测试:
@ContextConfiguration(classes = SpringDataJPAConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class OneToOneTest {
@Autowired
private CustomerRepository repository;
@Test
public void testC(){
Account account = new Account();
account.setUsername("jack");
Customer customer = new Customer();
customer.setCustName("jackAccountName");
customer.setAccount(account);
account.setCustomer(customer);
repository.save(customer);
}
}
一对多与多对一其实是类似的,只不过站的角度不同
一个客户有收到多条信息
①配置管理关系
public class Customer{
...
// 一对多
// fetch 默认是懒加载 懒加载的优点( 提高查询性能)
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JoinColumn(name="customer_id")
private List<Message> messages;
}
②配置关联操作
一条消息可以发给多个用户
①配置管理关系
public class Customer{
...
@ManyToOne
@JoinColumn(name="customer_id")
private List<Message> message;
}
②配置关联操作
public class Message{
//多对一
@ManyToOne(cascade = CascadeType.PERSIST)
private Customer customer;
}
客户与角色,多对多
此处以单向多对多为例:
①配置管理关系
public class Customer{
/*
中间表需要设置@JoinTable来维护外键(不设置也会自动生成)
name:指定中间表名称
joinColumns 设置本表的外键名称
inverseJoinColumns 设置关联表的外键名称
*/
@ManyToMany
@JoinTable(
name = "tb_customer_role",
joinColumns={@JoinColumn(name="c_id")},
inverseJoinColumns={@JoinColumn(name="r_id")}
)
private List<Role> roles;
}
②配置关联操作
…
③测试
// 保存
/*
如果保存的关联数据 希望使用已有的 ,就需要从数据库中查出来(持久状态)。否则 提示 游离状态不能持久化
如果一个业务方法有多个持久化操作, 记得加上@Transactional ,否则不能共用一个session
在单元测试中用到了@Transactional , 如果有增删改的操作一定要加@Commit
单元测试会认为你的事务方法@Transactional, 只是测试而已, 它不会为你提交事务, 需要单独加上 @Commit
*/
@Test
@Transactional
@Commit
public void testC() {
List<Role> roles=new ArrayList<>();
roles.add(roleRepository.findById(9L).get());
roles.add(roleRepository.findById(10L).get());
Customer customer = new Customer();
customer.setCustName("Jim");
customer.setRoles(roles);
repository.save(customer);
}
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
# 应用服务 WEB 访问端口
server.port=8080
#没有表的时候创建
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/springdata_jpa?serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
# 打印SQL
spring.jpa.show-sql=true
# 格式化SQL
spring.jpa.properties.hibernate.format_sql = true
User实体类:
@Table(schema = "tb_user")
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String userName;
@Column(name = "age")
private Integer userAge;
}
UserRepository:
//Integer对应的是User的Id类型
public interface UserRepository extends CrudRepository<User, Integer> {
}
测试类:
@SpringBootTest
public class Test01 {
@Autowired
private UserRepository repository;
@Test
public void testC(){
User user = new User();
user.setUserAge(3);
user.setUserName("Jackson");
repository.save(user);
}
}
@Import(JpaRepositoriesImportSelector.class)
JpaRepositoriesImportSelector又会注册一个ImportBeanDefinitionRegistrar 其实就是JpaRepositoriesRegistrar
‐‐‐‐JpaRepositoriesRegistrar 跟通过@EnableJpaRepositories 导入进来的组件是同一个
就相当于 @EnableJpaRepositories