springboot整合SpringDataJPA

纵然是瞬间 提交于 2020-02-26 18:12:17

什么是SpringDataJPA?

① SpringData是spring提供的操作数据的框架,jpa是其中的一个模块。
②SpringDataJPA用于简化操作持久层(操作数据库)的代码,只需要编写接口即可。JPA会根据你提供的实体类和持久层接口,自动创建数据库表并插入数据保存。意味着你不需要自己去创建数据库表!!!


SpringBoot整合SpringDataJPA步骤

  • 在pom文件中添加坐标
<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
	<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>

<dependencies>
	<!-- spring-boot启动器 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<!-- 添加thymeleaf启动器 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<!-- devtools坐标 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-devtools</artifactId>
		<scope>runtime</scope>
		<optional>true</optional>
	</dependency>
	<!-- jpa启动器 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<!-- junit启动器 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<!-- mysql数据库驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
	<!-- druid数据库连接池 -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>1.1.10</version>
	</dependency>
</dependencies>
  • 编写全局配置文件application.properties
# mysql-message
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=leidada
# datasource-pool
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# spring-data-jpa
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
  • 添加实体类 (重点)
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity					//声明实体类
@Table(name="t_users")	//表示和数据库中的哪张表整合
public class Users {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)	//主键
	@Column(name="id")	//数据库表中对应id属性的列名
	private Integer id;
	
	@Column(name="username")
	private String username;
	
	@Column(name="password")
	private String password;
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "Users [id=" + id + ", username=" + username + ", password=" + password + "]";
	}
}
  • 编写Dao接口 (重点)

JpaRepository<T, ID>,参数含义
①参数一(T):当前需要映射的实体;
②参数二(ID):当前映射的实体中的主键(OID)的类型;

public interface UsersRepository extends JpaRepository<Users, Integer>{
}
  • 创建启动类
@SpringBootApplication
public class App {
	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}
  • 编写测试代码

期望的测试结果
①如果实体类中@Table(name="t_users")t_users这张表不存在,创建t_users这张表。
②将数据插入这张表中。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private UsersRepository usersRepository;
	@Test
	public void saveUser() {
		Users user = new Users();
		user.setUsername("张三");
		user.setPassword("zs");
		usersRepository.save(user);
	}
}

测试结果

如果程序执行正确,则控制台会输出一下两句sql语句。

Hibernate: create table t_users (id integer not null auto_increment, password 
Hibernate: insert into t_users (password, username) values (?, ?)


SpringDataJPA提供的核心接口

  • Repository接口
  • CrudRepository接口
  • PagingAndSortingRepository接口
  • JpaReposiroty接口
  • JPASpecificationExecutor接口


SpringDataJPA-Repository接口使用

①提供了方法名称命名查询方式;注意下面表格的命名规则
关键字 属性名 查询条件
findBy 实体类中的属性名,首字母大写 Is、Equals、默认(相等方式);
findBy 实体类中的属性名,首字母大写 并列:And、或者:Or;
findBy 实体类中的属性名,首字母大写 模糊查询:Like;
  • 编写接口
/**
 * Repository接口的方法名称命名查询
 */
public interface UsersRepositoryByName extends Repository<Users, Integer> {
	List<Users> findByUsername(String username);
	
	List<Users> findByUsernameAndPassword(String username, String password);
	
	List<Users> findByUsernameLike(String username);
}
  • 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private UsersRepositoryByName usersRepositoryByName;

	@Test
	public void testFindByName() {
//		List<Users> list = this.usersRepositoryByName.findByUsername("张三");
//		List<Users> list = this.usersRepositoryByName.findByUsernameAndPassword("张三",	"zs");
		List<Users> list = this.usersRepositoryByName.findByUsernameLike("张%");
		for(Users s : list) {
			System.out.println(s);
		}
	}
}

②提供了基于@Query注解查询与更新;
  • 编写接口
注意
@Query注解默认支持HQL,其属性nativeQuery=false(默认);使用标准SQL,要设置nativeQuery=true
@Query对方法名称没有要求,当方法参数数量多时,推荐使用该方法,否则会造成代码可读性降低。
@Query用于更新操作时,需要使用@Modifying搭配使用。
/**
 * Repository接口的@Query查询操作
 */
public interface UsersRepositoryQueryAnnotation extends Repository<Users, Integer> {
	//使用HQL
	@Query("from Users where username=?1")
	List<Users> queryByUsernameUseHQL(String username);
	
	//使用标准SQL
	@Query(value="select * from t_users where username=?1", nativeQuery=true)
	List<Users> queryByUsernameUseSQL(String username);
	
	//执行更新操作
	@Query("update Users set password = ?1 where id = ?2")
	@Modifying	//需要执行一个更新操作
	void updateUsersByIdHQL(String password, Integer id);
}
  • 编写测试代码
注意
@Transactional注解与@Test注解一起使用时,事务是自动回滚的。需要使用@Rollback(false) ,取消自动回滚
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private UsersRepositoryQueryAnnotation usersRepositoryQueryAnnotation;
	
	//查询操作
	@Test
	public void testQueryAnnotation() {
//		List<Users> list = this.usersRepositoryQueryAnnotation.queryByUsernameUseHQL("张三");
		List<Users> list = this.usersRepositoryQueryAnnotation.queryByUsernameUseSQL("张三");
		for(Users s : list) {
			System.out.println(s);
		}
	}
	//更新操作
	@Test
	@Transactional
	@Rollback(false)	
	public void testUpdateUsersById() {
		this.usersRepositoryQueryAnnotation.updateUsersByIdHQL("zss", 1);
	}
}
  • 错误:JDBC style parameters (?) are not supported for JPA queries.
    解决:@Query("from Users where username=?1"),在?后面加上参数的index,低版本的jpa不加也可以运行。


SpringDataJPA-CrudRepository接口使用

CrudRepository接口主要是完成增删改查的作用,继承了Repository接口。

  • 创建接口
public interface UsersCrudRepository extends CrudRepository<Users, Integer> {
}
  • 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private UsersCrudRepository usersCrudRepository;

	@Test
	public void testCrudRepository() {
		//添加
		Users user = new Users();
		user.setUsername("张三");
		user.setPassword("zs");
		this.usersCrudRepository.save(user);
		//更新
		Users user1 = new Users();
		user1.setId(2);
		user1.setUsername("张三");
		user1.setPassword("zss");
		this.usersCrudRepository.save(user1);
		//查询单个
		Optional<Users> user2 = this.usersCrudRepository.findById(2);
		System.out.println(user2);
		//删除
		this.usersCrudRepository.deleteById(2);
	}
}


SpringDataJPA-PagingAndSortingRepository接口使用

该接口提供了分页和排序功能,但是只能对查询全部进行排序和分页,继承了CrudRepository。下面的JPASpecificationExecutor接口会对该功能进行优化。

排序对象 作用
Order 定义排序规则
Sort 封装排序规则
分页对象 作用
Pageable 封装了分页的参数,包括当前页、每页显示的条数。注意:当前页从0开始。 Pageable pageable = new PageRequest(page, size);
  • 创建接口
public interface PagingAndSortingRepository
		extends org.springframework.data.repository.PagingAndSortingRepository<Users, Integer> {
}
  • 编写测试代码
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private PagingAndSortingRepository pagingAndSortingRepository;
	
	@Test
	public void testPagingAndSortRepository() {
		//排序
		//order定义排序规则,根据id降序
		Order order = new Order(Direction.DESC, "id");
		Sort sort = new Sort(order);
		List<Users> users = (List<Users>) this.pagingAndSortingRepository.findAll(sort);
		for(Users u : users) {
			System.out.println(u);
		}
		
		//分页
		Pageable pageable = new PageRequest(0, 1);
		Page<Users> page = this.pagingAndSortingRepository.findAll(pageable);
		System.out.println("总条数:"+page.getTotalElements());
		System.out.println("总页数:"+page.getTotalPages());
		List<Users> list = page.getContent();
		for(Users u : list) {
			System.out.println(u);
		}
		
		//排序+分页
		Order order1 = new Order(Direction.DESC, "id");
		Sort sort1 = new Sort(order1);
		Pageable pageable1 = new PageRequest(0, 2, sort1);
		Page<Users> page1 = this.pagingAndSortingRepository.findAll(pageable1);
		System.out.println("总条数:"+page1.getTotalElements());
		System.out.println("总页数:"+page1.getTotalPages());
		List<Users> list1 = page1.getContent();
		for(Users u : list1) {
			System.out.println(u);
		}
	}
}


SpringDataJPA-JpaRepository接口使用

该接口继承了PagingAndSortingRepository接口。对继承的父接口的方法的返回值进行适配。主要的作用就是对其所继承的所有父接口的返回值做了修改,不用再进行强制类型转换,开发中常用。

  • 编写接口
public interface UsersRepository extends JpaRepository<Users, Integer>{

}
  • 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class UsersRepositoryTest {
	@Autowired
	private UsersRepository usersRepository;
	@Test
	public void testJpaRepositorySort() {
		//order定义排序规则,根据id降序
		Order order = new Order(Direction.DESC, "id");
		//sort对象封装了排序规则
		Sort sort = new Sort(order);
		List<Users> users = this.usersRepository.findAll(sort);
		for(Users u : users) {
			System.out.println(u);
		}
	}
}


SpringDataJPA-JPASpecificationExecutor接口使用

该接口提供了多条件查询或者复杂查询的方法,并且可在查询中添加分页和排序功能。可以根据自己的需求进行查询。

注意
该接口是单独存在的!!!没有继承以上任何一种接口。但是会同JpaRepository等接口一起使用!!!
  • 编写接口
public interface UsersJPASpecificationExecutor extends JpaRepository<Users, Integer>,JpaSpecificationExecutor<Users> {

}
  • 编写测试方法
参数类型 作用
Specification 用于封装查询条件,使用匿名内部类的方式使用,可以少维护一个class。Specification<Users> spec = new Specification<Users>() {}
Predicate 封装了单个查询条件。
Root<T> 查询对象的属性封装,如:Root<Users>即Users的属性都在root中。
CriteriaQuery<?> 封装了我们要执行的查询中的各个部分的信息。如:select、from、order、by等。
CriteriaBuilder 查询条件构造器,定义不同的查询条件。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class JpaSpecificationExecutorTest {
	@Autowired
	private UsersJPASpecificationExecutor usersJPASpecificationExecutor;
	
	/**
	 * 单条件测试
	 */
	@Test
	public void testJPASpecificationExecutor1() {
		/**
		 *	Specification用于封装查询条件
		 */
		Specification<Users> spec = new Specification<Users>() {
			//Predicate封装了单个查询条件
			@Override
			public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				/**
				 * sql条件:where username= '张三'
				 * Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
				 */
				Predicate pre = cb.equal(root.get("username"), "张三");
				return pre;
			}
			
		};
		List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
		for(Users u : users) {
			System.out.println(u);
		}
	}
	
	/**
	 * 多条件测试,方法一
	 */
	@Test
	public void testJPASpecificationExecutor2() {
		/**
		 *	Specification用于封装查询条件
		 */
		Specification<Users> spec = new Specification<Users>() {
			//Predicate封装了单个查询条件
			@Override
			public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				/**
				 * sql条件:where username= '张三' and password='zs'
				 * Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
				 */
				List<Predicate> list = new ArrayList<>();
				list.add(cb.equal(root.get("username"), "张三"));
				list.add(cb.equal(root.get("password"), "zs"));
				Predicate[] arr = new Predicate[list.size()];
				return cb.and(list.toArray(arr));
			}
			
		};
		List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
		for(Users u : users) {
			System.out.println(u);
		}
	}
}
/**
 * 多条件测试,方法二
 */
@Test
public void testJPASpecificationExecutor3() {
	/**
	 *	Specification用于封装查询条件
	 */
	Specification<Users> spec = new Specification<Users>() {
		//Predicate封装了单个查询条件
		@Override
		public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			/**
			 * sql条件:where username= '张三' and password='zs'
			 * Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
			 */
			//where (username='张三' and password='zs') or id='3'
			return cb.or(cb.and(cb.equal(root.get("username"), "张三"),cb.equal(root.get("password"), "zs")),cb.equal(root.get("id"), "3"));
		}
		
	};
	List<Users> users = this.usersJPASpecificationExecutor.findAll(spec);
	for(Users u : users) {
		System.out.println(u);
	}
}
/**
 * 多条件测试、排序
 */
@Test
public void testJPASpecificationExecutor3() {
	/**
	 *	Specification用于封装查询条件
	 */
	Specification<Users> spec = new Specification<Users>() {
		//Predicate封装了单个查询条件
		@Override
		public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			/**
			 * sql条件:where username= '张三' and password='zs'
			 * Predicate参数含义:参数一:要查询的属性;参数二:查询条件的值。
			 */
			//where (username='张三' and password='zs') or id='3'
			return cb.or(cb.and(cb.equal(root.get("username"), "张三"),cb.equal(root.get("password"), "zs")),cb.equal(root.get("id"), "3"));
		}
		
	};
	//排序
	Sort sort = new Sort(new Order(Direction.DESC,"id"));
	List<Users> users = this.usersJPASpecificationExecutor.findAll(spec, sort);
	for(Users u : users) {
		System.out.println(u);
	}
}


关联映射操作

一对多的关联关系
  • 需求:角色与用户的一对多的关联关系,一个角色对应多个用户;
    角色:一方;
    用户:多方;
注解 作用
@ManyToOne 多对一,写在多方实体类中。
@JoinColumn(name=“外键”) 维护外键,写在多方实体类中。
@OneToMany 一对多,写在一方实体类中。
  • 实体类代码:
注意
因为下面实体类存在关联属性,所以在写toString方法时,不要把关联对象也写入,否则会出现java.lang.StackOverflowError 错误,因为对象之间相互打印,导致了栈溢出错误!!!
@Entity					//声明实体类
@Table(name="t_users")	//表示和数据库中的哪张表整合
public class Users {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)	//主键
	@Column(name="id")	//数据库表中对应id属性的列名
	private Integer id;
	
	@Column(name="username")
	private String username;
	
	@Column(name="password")
	private String password;
	
	//建立用户和角色的关联关系,一个用户对应一个角色。
	@ManyToOne(cascade=CascadeType.PERSIST)	//多对一关系,添加用户的同时添加角色
	@JoinColumn(name="role_id")	//维护外键
	private Roles role;
	//get \ set \ toString不包含关联对象
}
@Entity
@Table(name="t_roles")
public class Roles {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="role_id")
	private Integer role_id;
	@Column(name="role_name")
	private String role_name;
	
	//建立用户和角色的关联关系,一个角色对应多个用户。
	@OneToMany(mappedBy="role",fetch=FetchType.EAGER)		//一对多关系
	private Set<Users> users = new HashSet<>();
	
	//get \ set \ toString不包含关联对象
}
  • 测试一对多的关联关系
public interface UsersRepository extends JpaRepository<Users, Integer>{
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class OneToManyTest {
	@Autowired
	private UsersRepository usersRepository;
	/**
	 * 一对多,添加
	 */
	@Test
	public void testSave() {
		//第一步:创建一个用户
		Users user = new Users();
		user.setUsername("李四");
		user.setPassword("ls");
		//第二步:创建一个角色
		Roles role = new Roles();
		role.setRole_name("管理员");
		//第三步:关联
		role.getUsers().add(user);
		user.setRole(role);
		//第四步:保存
		this.usersRepository.save(user);
	}
	
	/**
	 * 一对多,查询
	 */
	@Test
	public void testSelect() {
		Optional<Users> user = this.usersRepository.findById(4);
		System.out.println(user);
		Users users = user.get();
		System.out.println(users.getRole().getRole_name());
	}
}
  • 结果
Hibernate: insert into t_roles (role_name) values (?)
Hibernate: insert into t_users (password, role_id, username) values (?, ?, ?)

Optional[Users [id=4, username=李四, password=ls]]
管理员

多对多的关联关系
  • 需求:角色与菜单的多对多关系;一个菜单有多个角色,一个角色对应多个菜单;
  • 角色:多方;
  • 菜单:多方;
  • 编写实体类
注解 作用
@ManyToMany 多对多关系。在多对多的哪一侧添加都可以。(cascade=CascadeType.PERSIST) 在关联后一起保存到数据库。fetch=FetchType.EAGER将延迟加载修改为立即加载。
@JoinTable 映射中间表。name 表名、joinColumns当前表中的主键所关联的中间表中的外键字段、inverseJoinColumns另一张表中的主键所关联的中间表中的外键字段。
@Entity
@Table(name="t_roles")
public class Roles {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="role_id")
	private Integer role_id;
	
	@Column(name="role_name")
	private String role_name;
	
	//建立角色和菜单的关联关系
	@ManyToMany(cascade=CascadeType.PERSIST,fetch=FetchType.EAGER)
	@JoinTable(name="t_roles_menus",joinColumns=@JoinColumn(name="role_id"),inverseJoinColumns=@JoinColumn(name="menus_id")) //@JoinTable:映射中间表
	private Set<Menus> menus  = new HashSet<>();
}
@Entity
@Table(name="t_menus")
public class Menus {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="menus_id")
	private Integer menus_id;
	
	@Column(name="menus_name")
	private String menus_name;
	
	@Column(name="menus_url")
	private String menus_url;
	
	@Column(name="father_id")
	private String father_id;

	//建立角色和菜单的关联关系,多个Menus对应多个角色
	@ManyToMany(mappedBy="menus")	//对应role中的menus对象名
	private Set<Roles> roles = new HashSet<>();
}
  • 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=App.class)
public class ManyToManyTest {
	@Autowired
	private RolesRepository rolesRepository;
	@Test
	public void testSave() {
		//第一步:创建角色对象
		Roles role = new Roles();
		role.setRole_name("项目经理");
		//第二步:创建菜单对象
		Menus menus = new Menus();
		menus.setMenus_name("xxxx管理系统");
		menus.setFather_id("0");
		
		Menus menus2 = new Menus();
		menus.setFather_id("1");
		menus.setMenus_name("项目管理");
		//第三步:关联--@ManyToMany(cascade=CascadeType.PERSIST)
		role.getMenus().add(menus);
		role.getMenus().add(menus2);
		menus.getRoles().add(role);
		menus2.getRoles().add(role);
		//保存
		this.rolesRepository.save(role);
	}
	
	/**
	 * 测试查询
	 */
	@Test
	public void testSelect() {
		Optional<Roles> roles = this.rolesRepository.findById(2);
		System.out.println(roles);
		Roles role = roles.get();
		System.out.println(role);
		System.out.println(role.getMenus());
	}
}
  • 结果
Hibernate: insert into t_roles (role_name) values (?)
Hibernate: insert into t_menus (father_id, menus_name, menus_url) values (?, ?, ?)
Hibernate: insert into t_menus (father_id, menus_name, menus_url) values (?, ?, ?)
Hibernate: insert into t_roles_menus (role_id, menus_id) values (?, ?)
Hibernate: insert into t_roles_menus (role_id, menus_id) values (?, ?)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!