选择了,web,MySQL,JPA组件作为我们开发必备组件
JPA(Java Persistence API)是Sun官方提出的Java持久化规范,用来方便大家操作数据库。
真正干活的可能是Hibernate,TopLink等等实现了JPA规范的不同厂商,默认是Hibernate。
QueryDslhttps://www.jianshu.com/p/4e9d8adaeeb2
配置数据源以及JPA
配置DataSource以及JPA
用application.yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUniCode=true&characterEncoding=utf8&useSSL=false
driverClassName: com.mysql.jdbc.Driver
username: admin
password: admin
jpa:
database: MySQL
show-sql: true
hibernate:
naming: #命名策略
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #无修改命名
#physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy #遇到大写字母 加”_”的命名
#properties:
#ddl-auto: update #自动创建表结构,可以不建表了
ddl-auto:create----每次运行该程序,没有表格会新建表格,表内有数据会清空
ddl-auto:create-drop----每次程序结束的时候会清空表
ddl-auto:update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新
ddl-auto:validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错
show-sql用来在控制台输出JPA自语句。
创建实体
我们根据数据库中的字段对应创建一个UserEntity来作为对应操作 实体类注解 主键生成策略
@Transient 不添加在表中
映射枚举对象到数据库就需要使用@Enumerated注释进行标注。
@Enumerated(EnumType.STRING)
//@Column(name="address_type")
private AddressType type;//地址类型
package com.cxy.favourite.domain;
import javax.persistence.*;
import java.io.Serializable;
/**
* 用户
*/
@Entity
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //Mysql为@GenerationType(strategy = GenerationType.IDENTITY)主键
private Long id;
@Column(nullable = false,unique = true)
private String userName;//=nickName
@Column(nullable = false)
private String salt;
@Column(nullable = false)
private String password;
@Column(nullable = false,unique = true)
private String email;
@Column(nullable = true)
private String myPicture;//头像
@Column(length = 65535,nullable = true,columnDefinition = "Text")
private String introduction;
@Column(nullable = true)//TODO 之后改成false
private Long createdTime;
@Column(nullable = true)//TODO 之后改成false
private Long lastModifyTime;//最后修改时间
@Column(nullable = true)
private String outDate;//作废时间
@Column(nullable = true)
private String validataCode;//验证码
@Column(nullable = true)
private String backgroundPicture;
public User() {
super();
}
public User(String userName,String password, String salt, String email) {
super();
this.userName = userName;
this.password = password;
this.salt = salt;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMyPicture() {
return myPicture;
}
public void setMyPicture(String myPicture) {
this.myPicture = myPicture;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public Long getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Long createdTime) {
this.createdTime = createdTime;
}
public Long getLastModifyTime() {
return lastModifyTime;
}
public void setLastModifyTime(Long lastModifyTime) {
this.lastModifyTime = lastModifyTime;
}
public String getOutDate() {
return outDate;
}
public void setOutDate(String outDate) {
this.outDate = outDate;
}
public String getValidataCode() {
return validataCode;
}
public void setValidataCode(String validataCode) {
this.validataCode = validataCode;
}
public String getBackgroundPicture() {
return backgroundPicture;
}
public void setBackgroundPicture(String backgroundPicture) {
this.backgroundPicture = backgroundPicture;
}
}
创建JPA
实体类已经创建完成了,接着需要使用SpringDataJPA来完成数据库操作,新建名字叫做jpa的package,然后创建UserJPA接口并且继承SpringDataJPA内的接口作为父类,当然还有序列化接口
package com.cxy.jpa;
import com.cxy.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;//简单数据操作
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;//复杂查询
import java.io.Serializable;//序列化接口
public interface UserRepository extends JpaRepository<User,Long>,JpaSpecificationExecutor<User>,Serializable{
}
继承了JpaRepository接口(SpringDataJPA提供的简单数据操作接口)、JpaSpecificationExecutor(SpringDataJPA提供的复杂查询接口)、Serializable(序列化接口)。
其中,JpaRepository接口内有又继承了PagingAndSortingRepository接口以及QueryByExampleExecutor接口,
而PagingAndSortingRepository接口内部又有一个继承自CrudRepository接口
CrudRepository
该接口内包含了最简单的CRUD也就是Create、Read、Update、Delete方法,当然还有count、exists方法
PagingAndSortingRepository
包含了分页,排序等功能
QueryByExampleExecutor
JpaRepository接口继承了该接口,这个接口提供条件查询,复杂查询方法
注意:SpringDataJPA提供的复杂条件查询,需要进行QueryDSL,很复杂, 与Mybatis相比,并不是最好的选择。
不需要做其他的任何操作了,因为SpringBoot以及SpringDataJPA会为我们全部搞定,SpringDataJPA内部使用了类代理的方式让继承了它接口的子接口都以spring管理的Bean的形式存在,也就是说我们可以直接使用@Autowired注解在spring管理bean使用,现在数据库层几乎已经编写完成。
控制层编写查询方法
UserController中,然后创建一个查询,添加,更新,删除方法
package com.cxy.favourite.controller;
import com.cxy.favourite.domain.User;
import com.cxy.favourite.jpa.UserRepository;
import org.hibernate.sql.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 测试 JpaResposity
*/
//@RestController
@RequestMapping("test")
public class TestController {
@Autowired
private UserRepository userRepository;
/**
* 查询用户列表方法
*
* @return
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public List<User> list() throws Exception{
return userRepository.findAll();
}
/**
*增加,更新用户方法,添加后返回List,方便测试
* @Param entity
* @return
*/
@RequestMapping(value = "/save",method = RequestMethod.POST)
public List<User> save(User entity) throws Exception{
userRepository.save(new User("张三", "123456", "1234r","66666666@qq.com"));
userRepository.save(new User("小秘书", "123456", "12ggg","12345@qq.com"));
//userJPA.save方法可以执行添加也可以执行更新,
// 如果需要执行持久化的实体存在主键值则更新数据,如果为0则添加数据。
return userRepository.findAll();
}
/**
* 删除用户方法,删除后返回List,方便测试
*
* @return
*/
@RequestMapping(value = "/{id}/delete",method = RequestMethod.GET)
public List<User> delete(@PathVariable("id")Long id) throws Exception
{
User zhang3 = this.userRepository.getOne(id);
userRepository.delete(zhang3);
return userRepository.findAll();
}
}
常见bug
1.启动失败的问题('hibernate.dialect' not set)
Hibernate SQL方言没有设置导致的,在properties文件中增加下面这行:
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
2.The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one时区错误
application.properties 的adtasource.url加上
&serverTimezone=GMT%2B8
@Query注解自定义SQL,进行动态SQL
两种方式:
- 注解
- 使用EntityManager(点我进入)
@Query是用来配置自定义SQL的注解
nativeQuery = true才是表明了使用原生的sql,如果不配置,默认是false,则使用HQL查询方式。
public interface CategoryDao extends JpaRepository<Category,Integer>,JpaSpecificationExecutor<Category>,Serializable {
/*查询名字是nb的数据*/
@Query(value = "select * from category_ where name = ?1",nativeQuery = true)
public List<Category> nativeQuery (String name);
}
@RequestMapping("/select")
public List<Category> queryNB(){
return categoryDao.nativeQuery("nb");
}
@Query配合@Modifying 完成自定义条件操作
@Modify的clearAutomatically=true的作用
可以清除底层持久化上下文,就是entityManager这个类,我们知道jpa底层实现会有二级缓存,也就是在更新完数据库后,如果后面去用这个对象,你再去查这个对象,这个对象是在一级缓存,但是并没有跟数据库同步,这个时候用clearAutomatically=true,就会刷新hibernate的一级缓存了, 不然你在同一接口中,更新一个对象,接着查询这个对象,那么你查出来的这个对象还是之前的没有更新之前的状态。
从名字上可以看到我们的@Query注解好像只是用来查询的,但是如果配合@Modifying注解一共使用,则可以完成数据的删除、添加、更新操作。现在,根据id 和name 结合删除一条数据
/*根据id,name删除数据*/
@Modifying(clearAutomatically=true)
@Query(value = "delete from category_ where id=?1 and name=?2",nativeQuery = true)
public void deleteByIdAndName(Integer id,String name);
@RequestMapping("/modify")
public List<Category> modify(){
categoryDao.deleteByIdAndName(5,"nb");
return categoryDao.findAll();
}
原因:抛出的异常TranscationRequiredException,意思就是你当前的操作给你抛出了需要事务异常,SpringDataJPA自定义SQL时需要在对应的接口或者调用接口的地方(service)添加事务注解@Transactional,来开启事务自动化管理。
/**
* 设置名字(原生sql)
* @param introduction
* @param email
* @return
*/
@Modifying(clearAutomatically=true)
@Transactional(rollbackFor = RuntimeException.class)
@Query(value = "update user set user_name=?1 where email=?2",nativeQuery=true)
int setUserName(@Param("userName") String userName, @Param("email") String email);
/**
* 设置名字(操作entity)
* @param introduction
* @param email
* @return
*/
@Modifying(clearAutomatically=true)
@Transactional(rollbackFor = RuntimeException.class)
@Query("update User set userName=:userName where email=:email")
int setUserName(@Param("userName") String userName, @Param("email") String email);
设计思想:
自定义BaseDao
正常情况下不仅仅只继承一个JpaRepository接口,难道业务数据接口每一个都去继承几个相同的接口?答案肯定是 NO,当然多个继承也是可以的,不过对于系统设计还有代码复用性来说并不是最好的选择!
我们创建一个包名叫做base,在包内添加一个BaseRepository接口,并且接口继承我们的JpaRepositor,
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import java.io.Serializable;
/**
* jpa父接口
* @NoRepositoryBean指明当前这个接口不作为一个Repository创建代理实现类。
* 用来配置父接口不参与jpa的代理
*/
@NoRepositoryBean
public interface BaseDao<T,ID extends Serializable> extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> {
}
之后创建的接口,直接继承BaseRepository就行了,继承的子接口会拥有JpaRepository,JpaSpecificationExecutor所有方法实现。
查询多个数据如何返回
@Query(value = "select u.userName , u.introduction ,u.myPicture ,u.id from Follow f ,User u where f.userId=?1 and f.followId = u.id and f.status = 'FOLLOW'")
List<Object[]> findFollowUserByUserId(@Param("userId") Long userId);
1.再写个DTO,(利用Collection.stream() )包装这些返回的数据(还可以考虑使用QueryDsl进行处理)
package com.cxy.favourite.domain.DTO;
import com.cxy.favourite.domain.Entitys;
/**
* 查询返回对象
*/
public class UserDTO extends Entitys {
private String userName;
private String introduction;
private String myPicture;
private Long userId;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getIntroduction() {
return introduction;
}
public void setIntroduction(String introduction) {
this.introduction = introduction;
}
public String getMyPicture() {
return myPicture;
}
public void setMyPicture(String myPicture) {
this.myPicture = myPicture;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
@Test
public void findFollowedUserByFollowId() throws Exception {
List<Object[]> result = this.followRepository.findFollowedUserByFollowId(3L);
List<UserDTO> list = result.stream().map(tuple ->{
//创建dto
UserDTO dto = new UserDTO();
dto.setUserName(tuple[0].toString());
dto.setIntroduction(tuple[1].toString());
dto.setMyPicture(tuple[2].toString());
dto.setUserId((Long)tuple[3]);
return dto;
}).collect(Collectors.toList());
System.out.println(list);
}
2.
List<Object> resultList = new ArrayList<>();
List<Object[]> result = this.followRepository.findFollowUserByUserId(3L);
for(Object row: result){
Object[] rowArray = (Object[])row;
Map<String, Object> mapArr = new HashMap<String, Object>();
mapArr.put("userName",rowArray[0]);
mapArr.put("introduction",rowArray[1]);
mapArr.put("myPicture",rowArray[2]);
mapArr.put("userId",rowArray[3]);
resultList.add(mapArr);
}
//return resultList