SpringBoot整合SpringDataJpa [vaynexiao]

遇到的问题

1,springboot集成jpa返回Json报错 com.fasterxml.jackson.databind.exc.InvalidDefinitionException
https://blog.csdn.net/liu_yulong/article/details/84594771

2,org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: xxx; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: xxx
有可能是级联保存时,关联的实体类对象数据有问题,比如主键id没值,或者id重复,总是数据不能合法持久化

3,Not a managed type
一定有一个实体类没有@Table @Entity 或者主键属性没有@Id

待学习

https://www.bilibili.com/video/BV1Y4411W7Rx?p=32&spm_id_from=pageDriver
https://www.bilibili.com/video/BV12T4y1u71m?p=1
https://www.bilibili.com/video/BV1Vb411e7of

api总结
https://www.cnblogs.com/rulian/p/6434631.html

SpringDataJPA、JPA、hibernate关系

JPA只是一种规范,内部都是由接口和抽象类构建的;hibernate它是我们最初使用的一套由ORM思想构建的成熟框架,但是这个框架内部又实现了一套JPA的规范(实现了JPA规范定义的接口),所有也可以称hibernate为JPA的一种实现方式我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程);Spring Data JPA它是Spring家族提供的,对JPA规范有一套更高级的封装,是在JPA规范下专门用来进行数据持久化的解决方案。

主要接口

Repository: 是 Spring Data的一个核心顶层接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法。
CrudRepository:  继承Repository,提供增删改查方法,可以直接调用。
PagingAndSortingRepository: 继承CrudRepository,具有分页查询和排序功能
JpaRepository: 继承PagingAndSortingRepository,针对JPA技术提供的接口
JpaSpecificationExecutor: 可以执行原生SQL查询

CrudRepository

CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S var1);

    <S extends T> Iterable<S> saveAll(Iterable<S> var1);

    Optional<T> findById(ID var1);

    boolean existsById(ID var1);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> var1);

    long count();

    void deleteById(ID var1);

    void delete(T var1);

    void deleteAll(Iterable<? extends T> var1);

    void deleteAll();

PagingAndSortingRepository

PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort var1);

    Page<T> findAll(Pageable var1);

JpaRepository

JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);

pom

spring-boot-starter-data-jpa 依赖 spring-data-jpa

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.9.0.RELEASE</version>
        </dependency>

在这里插入图片描述

HelloWorld

提前说一声,这并不是jpa的helloworld,也不是springdatajpa的helloworld,而是springboot整合springdatajpa,因为这个是主流趋势啊。数据库是mysql8

yml

spring:
#   数据源
  datasource:
#     mysql8驱动
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  jpa:
    hibernate:
#      可选参数 ddl-auto 详细文章:https://www.cnblogs.com/qingmuchuanqi48/p/11616145.html
#      create 启动时删数据库中的表,然后创建,退出时不删除数据表
#      create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
#      update 如果启动时表格式不一致则更新表,原有数据保留
#      validate 项目启动表结构进行校验 如果不一致则报错
      ddl-auto: update
    show-sql: true

# ddl-auto: update 时,第一次运行程序需要自己手动建表,也建议这么做,因为create和create-drop可怕的要死,会在启动程序时先“删”
CREATE TABLE `jpa_user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `email` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `jpa_hobby` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `level` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  `userid` bigint DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

pom

		<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>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

entity

@Entity
@Table(name = "jpa_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String username;

    @Column(name = "email")
    private String email;

Repository
泛型为操作的bean类型 主键类型

public interface UserRepository extends JpaRepository<User, Long> { }

controller

@Controller
@RequestMapping(path = "/user/crud")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping(path = "/save")
    public User saveUser( @RequestParam String name, @RequestParam String email ) {
        User n = new User();
        n.setUsername(name);
        n.setEmail(email);
        System.out.println(n);
        return userRepository.save(n);
    }

    @GetMapping(path = "/deleteById")
    @ResponseBody
    public void deleteById(Long id) {
        userRepository.deleteById(id);
    }

    @GetMapping(path = "/deleteByUser")
    @ResponseBody
    public void deleteByUser(User user) {
        userRepository.delete(user);
    }

    @GetMapping(path = "/update")
    @ResponseBody
    private User updateUser(Long id, String name){
        Optional<User> optionalUser = userRepository.findById(id);
        optionalUser.get().setUsername(name);
        User u = userRepository.save(optionalUser.get());
        return u;
    }

    @GetMapping(path = "/findOne")
    @ResponseBody
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }

    @GetMapping(path = "/getOne")
    @ResponseBody
    public User getOne(Long id) {
        return userRepository.getOne(id);
    }

    @GetMapping(path = "/findAll")
    @ResponseBody
    public List<User> getAllUser() {
        return userRepository.findAll();
    }

}

测试

http://localhost:8080/user/crud/save?name=zhangsan&email=zhangsan123@qq.com
http://localhost:8080/user/crud/getOne?id=3

此时已经可以实现,但有时候更规范写法是增加一个自定义的接口,然后增加一个实现类,单实现类还是注入的UserRepository接口,因为这个继承了JpaRepository,所以UserRepository才是真正起交互作用的。

public interface UserService {
    User saveUser(User user);
    void deleteUser(User user);
    void deleteUser(Long id);
    User updateUser(User user);
    User findUser(Long id);
    Iterable<User> findAll();
}

注意,接口不加@Service注解,而是实现类加

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public User saveUser(User user) {
        return userRepository.save(user);
    }

}

此时controller当要要注入的是实现类。而不是UserRepository了

测试:http://localhost:8080/user/crud/saveUserByImpl?name=姚明&email=姚明@qq.com

@Controller
@RequestMapping(path = "/user/crud")
public class UserController {

    @Autowired
    private UserServiceImpl userServiceImpl;

    @GetMapping(path = "/saveUserByImpl")
    public User saveUser( @RequestParam String name, @RequestParam String email ) {
        User n = new User();
        n.setUsername(name);
        n.setEmail(email);
        System.out.println(n);
        return userServiceImpl.saveUser(n);
    }
    
}

常见注解

@Column
@Column(name = “name”, nullable = false) // @Column: 对应数据库列名,可选, nullable 是否可以为空, 默认true
@query

@Transactional
@NoRepositoryBean

@Temporal

数据库的字段类型有date、time、datetime
而Temporal注解的作用就是帮Java的Date类型进行格式化,一共有三种注解值:

第一种:@Temporal(TemporalType.DATE)——>实体类会封装成日期“yyyy-MM-dd”的 Date类型。

第二种:@Temporal(TemporalType.TIME)——>实体类会封装成时间“hh-MM-ss”的 Date类型。

第三种:@Temporal(TemporalType.TIMESTAMP)——>实体类会封装成完整的时间“yyyy-MM-dd hh:MM:ss”的 Date类型。

注解方式有两种:

写在字段上:

	@Temporal(TemporalType.TIMESTAMP)
    private Date birthday;	

写在 getXxx方法上:

    @Temporal(TemporalType.DATE)
    @Column(name = "birthday", length = 10)
    public Date getBirthday() {
        return this.birthday;
    }

@Entity

应用于实体类,表明该实体类被JPA管理,将映射到指定的数据库表

@Id

应用于实体类的属性或者属性对应的getter方法,表示该属性映射为数据库表的主键

@GerneratedValue

于@Id一同使用,表示主键的生成策略,通过strategy属性指定。

JPA提供的生成策略有:

  • AUTO — JPA自动选择合适的策略,是默认选项
  • IDENTITY — 采用数据库ID自增长的方式来生成主键值,Oracle不支持这种方式;
  • SEQUENCE — 通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式;
  • TABLE — 采用表生成方式来生成主键值,这种方式比较通用,但是效率低

Repository常用方法使用规范

关键字方法命名sql where字句
AndfindByNameAndPwdwhere name= ? and pwd =?
OrfindByNameOrSexwhere name= ? or sex=?
Is,EqualsfindById,findByIdEqualswhere id= ?
BetweenfindByIdBetweenwhere id between ? and ?
LessThanfindByIdLessThanwhere id < ?
LessThanEqualsfindByIdLessThanEqualswhere id <= ?
GreaterThanfindByIdGreaterThanwhere id > ?
GreaterThanEqualsfindByIdGreaterThanEqualswhere id > = ?
AfterfindByIdAfterwhere id > ?
BeforefindByIdBeforewhere id < ?
IsNullfindByNameIsNullwhere name is null
isNotNull,NotNullfindByNameNotNullwhere name is not null
LikefindByNameLikewhere name like ?
NotLikefindByNameNotLikewhere name not like ?
StartingWithfindByNameStartingWithwhere name like ‘?%’
EndingWithfindByNameEndingWithwhere name like ‘%?’
ContainingfindByNameContainingwhere name like ‘%?%’
OrderByfindByIdOrderByXDescwhere id=? order by x desc
NotfindByNameNotwhere name <> ?
InfindByIdIn(Collection<?> c)where id in (?)
NotInfindByIdNotIn(Collection<?> c)where id not in (?)
TruefindByAaaTuewhere aaa = true
FalsefindByAaaFalsewhere aaa = false
IgnoreCasefindByNameIgnoreCasewhere UPPER(name)=UPPER(?)

自定义实体类对应表名

场景:
在entity类中@Table(name = “cst_customer_#{appConfig.tableName}”)支持SpEl表达式替换

实现思路如下:

  • 自定义命名策略
  • 配置自定义策略
  • JavaConfig定义
  • @Table使用配置

原理总结:
配置命名策略后,写一个config类,类对象的属性值从yml中获取(但是此技术其实是yml固定写死的,并没有动态,想怎么更新值,就怎么更新值)

自定义命名策略

import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.springframework.beans.BeansException;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

/**
 * 自定义命名策略(实现支持解析#{javaConfig.property}获取表名功能 )
 *
 * @author  xxx
 * @since 2019-03-26
 */
@Component
public class MySpringPhysicalNamingStrategy extends SpringPhysicalNamingStrategy
        implements ApplicationContextAware {

    private final StandardEvaluationContext context = new StandardEvaluationContext();

    private final SpelExpressionParser parser = new SpelExpressionParser();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context.addPropertyAccessor(new BeanFactoryAccessor());
        this.context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        this.context.setRootObject(applicationContext);
    }

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        String nameStr = name.getText();
        if(nameStr.contains(ParserContext.TEMPLATE_EXPRESSION.getExpressionPrefix())){
            Expression expression = this.parser.parseExpression(nameStr, ParserContext.TEMPLATE_EXPRESSION);
            return Identifier.toIdentifier((String)expression.getValue(this.context, String.class));
        }else {
            //默认方式不变
            return super.toPhysicalTableName(name, jdbcEnvironment);
        }
    }

}

如果不配置命名策略就会报这样的错误
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL “create table cst_customer_#{app_config_table_name} (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id)) engine=InnoDB” via JDBC Statement

配置自定义策略

spring:
  jpa:
    show-sql: true
    hibernate:
      naming:
        physical-strategy: 包名.MySpringPhysicalNamingStrategy
myTableName: tb_user

JavaConfig定义

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AppConfig {

    @Value("${myTableName}")
    private String tableName;

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }
}

@Table使用配置

@Table(name = "cst_customer_#{appConfig.tableName}")

一对多级联保存

User实体对应多个Hobby实体,每个人都有多个爱好
在User增加Set属性,保存对应的多个hobby,以及两个注解 @OneToMany @JoinColumn 这种方式是在多的一方(Hobby)的表中增加一个外键列来保存关系

注意:这里说的是一对多的单向关联,不是一对多的双向关联。

@Entity
@Table(name = "jpa_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String username;

    @Column(name = "email")
    private String email;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) // 级联保存、更新、删除、刷新;延迟加载
    @JoinColumn(name="userid") // 在hobby表指定一个外键列来实现一对多的单向关联,这个列会动态创建
    private Set<Hobby> hobby;
@Entity
@Table(name = "jpa_hobby")
public class Hobby {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "level")
    private String level;

    @Column(name = "remark")
    private String remark;

用springboot自带的测试类DemoApplicationTests进行测试

测试前由于yml中ddl-auto: update每次都会删除旧表,新建表的,所以测试时最好删除两张表,从新开始测试,以免旧数据干扰

import com.example.demo.entity.Hobby;
import com.example.demo.entity.User;
import com.example.demo.repository.user.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashSet;
import java.util.Set;

@SpringBootTest
class DemoApplicationTests {

	@Autowired
	private UserRepository userRepository;

	@Test
	void contextLoads() {

		Set<Hobby> setHooby = new HashSet<>();
		for (int i = 0; i < 10; i++) {
			Hobby hobby = new Hobby();
			hobby.setName("篮球");
			hobby.setLevel("级别为"+i);
			hobby.setRemark("但是没有体育精神,垃圾指数"+i);
			setHooby.add(hobby);
		}
		User user = new User();
		user.setUsername("王五");
		user.setEmail("wangwu27@qq.com");
		user.setHobby(setHooby);
		userRepository.save(user);
	}

}

另一种方式:只在User类的hobby属性上加一个注解

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) // 级联保存、更新、删除、刷新;延迟加载

Hobby类不做任何变化,这样会多生成一张表,会以User的表名和下划线和Hobby的表名生成一张表user_hobby,来保存映射关系。注意,会多一张表,也许因为如此,会使数据库变得繁杂混乱,用的人比较少吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值