【boot开发】SpringBoot JPA + H2嵌入式
SpringBootSpringBoot开发技术工具类CommonUtil的创建使用到正则表达式,不清楚的看之前的blog,对于日期的格式化,we使用的是static块,在其中对成员变量daysLookup和englishFormater进行赋值,就是自定义一个日期格式化DateTimeFormatter,自定义的方式通过DateTimeFormatterBuilder,append自定义格式即可
SpringBoot
内容管理
- JPA配合H2进行持久化
-
- JPA注解
- JPA全自动 repository层继承CrudRepository
- Repository层功能测试 @DataJpaTest
- HTTP Restful风格
- 自动配置与外部配置
- 项目中使用h2嵌入式DB
-
- 自动注入三种方式 final + @RequiredArgsConstrutor
- @ConfigurationProperties 配置文件映射
- 响应中文乱码问题
- org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL via JDBC Statement
- org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'articleRepository': Invocation of init method failed;
SpringBoot开发技术
上一篇已经分享了springBoot的功能测试@SpringBootTest,导入起步依赖test,同时分享了关于SpringBoot时代使用外部服务器时的配置类SpringBootServletInitializer;
在功能测试中,模拟请求使用的是TestRestTemplate其getForEnitity就可以得到一个响应,指定响应的数据类型和访问的路径,就可以得到响应体ResponseEntity<响应数据类型>,通过juniper的assertThat就可以进行断言测试,为了能够使用@AfterEache,需要配置一个junit-platform.properties,指定testinstance.lifecycle.default为per_class
工具类CommonUtil的创建使用到正则表达式,不清楚的看之前的blog,对于日期的格式化,we使用的是static块,在其中对成员变量daysLookup和englishFormater进行赋值,就是自定义一个日期格式化DateTimeFormatter,自定义的方式通过DateTimeFormatterBuilder,append自定义格式即可【pattern,text,literal(按字面意义的)】
JPA配合H2进行持久化
JPA是一个全自动的持久层框架,相比Mybatis的半自动有快速搭建项目的优势,同时H2作为嵌入式数据库,可以快速测试Dao层,方便项目早期的编写和测试
使用mybatis或者mybatis-plus: yml中配置数据源【相关的sqlsessionFactoryBean和创建dao对象的MapperScannerConfiger就不需要配置了,自动装配】,在主启动类,加上MapperScan注解,扫描dao,就可以创建dao对象,如果分开xml文件,再yml中配置mapper-locations即可
而对于entity中的类不需要任何处理,因为只要表名和类名相同,字段名和属性名相同就可以进行自动的注入,这里借助的也是反射机制
JPA注解
对应的依赖就是data-jpa的starter
- @Entity : 标识实体类为JPA实体,程序运行时生成实体类的对应表,比如Article会默认生成的表为article【所以mybatis中对应就不需要指定】,也可以通过name指定表名 @Entity(name = “XXX”)
- @Table: 设置实体类在数据库中对应的表名
- @Id : 放在属性上,表明为表中的主键
- @GeneratedValue: 主键的生成策略【依赖具体的数据库】,默认是Auto自增
- @Column: 属性对应的字段名自定义设置
- @Transient:
标识当前属性不被持久化到数据库中; (之前提过),还可以使用transient进行修饰等 - @Temporal: 使用java.util的时间类型,使用这个注解进行转化
- @Enumerated: 映射枚举字段以String类型存
- @MappedSupperclass: 放在类上,表明不再注解被映射为一张表,而属性会映射为子表
相关关系
- @ManyToOne: 修饰的字段为另外一个entity表,该表与修饰表为多对一的关系,比如多篇Article对应一个User
这里使用JPA的一个不同就是我们需要对实体类加上@Entity注解,表明这个类为实体类,便于插入到H2数据库中【这里没有直接建立数据库】
@Entity
@Getter
@Setter
public class User {
@Id
@GeneratedValue
private Long id;
//登录名
private String loginName;
//名
private String firstName;
//姓
private String lastName;
//描述
private String description;
//相关的get和set方法
}
使用IDEA,旁边还会对该类进行形象化标识

JPA全自动 repository层继承CrudRepository
JPA全自动框架,和Mybaits的半自动相比要强许多,但是mybaits-plus也是全自动框架,在mybatis基础上功能增强
JPA全自动: repository层的repostory接口都继承BaseRepository接口
Mybatis-plus全自动: mapper层的mapper接口都继承BaseMapper接口
service层的service接口都继承IService接口,service实现类都继承ServiceImpl<mapper,entity>实现类;同时实现service接口
创建Repository的时候只需要继承CrudRepository即可,T代表实体类的类型,ID为主键的类型,剩余的工作JPA完成; JPA遵循约定优先配置,只要更具Spring和JPQL进行命名,就可以生成对应的SQL语句
public interface UserRepository extends CrudRepository<User,Long> {
//通过登录名查找用户
User findUserByLoginName(String loginName);
}
只是需要增加额外的方法即可,并且方法名按照Spring的约定,不需要编写sql【xml】,spring会对方法名进行提示
Repository层功能测试 @DataJpaTest
该功能注解结合@Runwith等使用,会完全禁用自动配置,并且只是应用于JPA的相关测试,测试的是使用嵌入式内存数据库, 要测试其他数据库,使用@SpringBootTest加@AutoConfigureTestDatabase
带有该注解的测试都是事务性的,测试结束都会回滚
TestEntityManager
TestEntityManager允许在测试中使用EntityManager,Spring repository是对entiry mamager的抽象,EnityManager就是JPA中用于增删查改的接口,相当于一座连接java对象和数据库的桥梁【之前的处理器测试依赖了TestRestTemplate,是用作响应测试的】可以将其看作对象处理的工具;而repository是数据库访问对象
persist方法就是持久化一个对象; 就是交给entityManager托管,相当于在内存库中【表中的记录】,之后flush一下,托管状态的数据对象就会插入数据库中
在使用repostory进行查询的时候,使用orElse(null) 表示没有找到返回空,否则会出问题
@DataJpaTest
public class RepostoriesTest {
@Resource
TestEntityManager entityManager;
@Resource
UserRepository userRepository;
@Resource
ArticleRepository articleRepository;
@Test
public void whenFindByIdOrNull_thenReturnArticle() {
//创建用户对象
User cfeng = new User();
cfeng.setLoginName("Cfeng");
cfeng.setFirstName("zhang");
cfeng.setLoginName("ning");
//托管用户对象,持久化
entityManager.persist(cfeng);
//创建一个文章对象
Article article = new Article();
article.setTitle("SpringBoot 开发技术");
article.setHeadline("简介:xxxxx");
article.setContent("springBoot作为一个快速上手工具,非常优秀");
article.setAuthor(cfeng);
//托管,将其持久化,借助的就是EntityManager
entityManager.persist(article);
//将托管状态的对象持久化到数据库中
entityManager.flush();
//根据ID查询文章
Article found = articleRepository.findById(article.getId()).orElse(null); //要使用orElse指定查询为空的结果
//断言保存前的对象与查询结果相等
assertThat(article).isEqualTo(found);
}
ApplicationRunner 容器创建监听器
事件驱动,就之前的Servlet中的ServletContextLinstener那种,实现这个接口其中的run方法,那么就会在项目启动的时候,执行run中的操作,所以经常应用比如开启数据库连接,读取配置文件之类的操作; 一般承担初始化的工作
@Configuration
public class BlogConfiguration implements ApplicationRunner {
//定义两个repository对象
private final UserRepository userRepository;
private final ArticleRepository articleRepository;
public BlogConfiguration(UserRepository userRepository,ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
this.userRepository = userRepository;
}
@Override
public void run(ApplicationArguments args) throws Exception {
//依次向数据库中插入几条数据,save方法的返回值就是插入的这个实例,如果成功
User saveUser = new User();
saveUser.setLoginName("xiaohuan");
saveUser.setFirstName("xiao");
saveUser.setLastName("huan");
User huanxiao = userRepository.save(saveUser);
//在插入两个文章对象
Article saveArticle1 = new Article();
saveArticle1.setTitle("前端教程");
saveArticle1.setHeadline("前端很火");
saveArticle1.setAuthor(saveUser);
saveArticle1.setContent("前端主要的几个就是.........");
Article article1 = articleRepository.save(saveArticle1);
//再插入一个
Article saveArticle2 = new Article();
saveArticle2.setTitle("后端教程");
saveArticle2.setHeadline("后端很火");
saveArticle2.setAuthor(saveUser);
saveArticle2.setContent("后端主要的几个就是.........");
Article article2 = articleRepository.save(saveArticle1);
}
}
这里的测试代码非常的easy,测试一下功能,再来写一下一体化测试integration
测试访问/artcle
在测试的时候想要发送rest风格请求,那么就需要进行字符串的拼接,这里要先进行slug格式的转换,所以需要使用String.format方法
String String.format(String fmt, Object… args);
这里就可以使用类似C中的printf的转为符,比如%s代表的就是字符串,%c字符,就会将后面的可变个数的参数按位置给插入到前面的格式串中
@Test
public void assertArticlePageTitle_Content_And_StatusCode() {
System.out.println(">> Assert article page title content and statusCode >>");
//模拟访问article页面,那么就要传入一个slug格式的title,使用TestRestTemplate进行访问
String title = "前端教程";
ResponseEntity<String> entity = restTemplate.getForEntity(String.format("/article/%s",CommonUtil.toSlug(title)),String.class);
//首先判断响应的状态码是否正常
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
//之后断言响应体中是否包含title,输出响应体
assertThat(entity.getBody()).contains(title,"前端很火","前端主要的几个就是.........");
System.out.println(entity.getBody());
}
提一下,之前的junit-platform.propertis是放在test的resources下面的
HTTP Restful风格
现在的前后端分离的趋势下,将项目改为符合restful风格的接口,访问的路径就应该是article模块,/api/article
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/")
public Iterable<User> findAll() {
return userRepository.findAll();
}
@GetMapping("/{loginName}")
public User findByLoginName(@PathVariable String loginName) {
User result = userRepository.findUserByLoginName(loginName);
//查询结果为空时返回404,抛出的是响应状态异常
if(result == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND,"the user does not exists");
}
return result;
}
}
其中的一部分实现,主要就是返回的都是响应体的数据,这里也需要进行测试,为了避免Dao对controller的影响,需要用Mockito模拟Dao
@WebMvcTest
public class HttpControllersTests {
@Resource
private MockMvc mockMvc;
@MockBean
private UserRepository userRepository;
@MockBean
private ArticleRepository articleRepository;
@Test
public void listAricles() throws Exception {
User saveUser = new User();
saveUser.setLoginName("xiaohuan");
saveUser.setFirstName("xiao");
saveUser.setLastName("huan");
Article saveArticle1 = new Article();
saveArticle1.setTitle("前端教程");
saveArticle1.setHeadline("前端很火");
saveArticle1.setAuthor(saveUser);
saveArticle1.setContent("前端主要的几个就是.........");
Article saveArticle2 = new Article();
saveArticle2.setTitle("后端教程");
saveArticle2.setHeadline("后端很火");
saveArticle2.setAuthor(saveUser);
saveArticle2.setContent("后端主要的几个就是.........");
//模拟articleRepository.findAllOrderByAddedAtDesc(),返回文章列表
//使用的mockito的when和then
when(articleRepository.findAllByOrderByAddedAtDesc()).thenReturn(Arrays.asList(saveArticle1,saveArticle2));
//diaoyong jiekou /api/article
mockMvc.perform(MockMvcRequestBuilders.get("/api/article"))
//期望的调用结果statuscode为2xx
.andExpect(status().is2xxSuccessful())
//期望的调用结果类型为JSON
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
//期望的返回值的内容,使用$.[0],$.[1]分别代指不同的数据对象
.andExpect(jsonPath("$.[0].author.loginName").value(saveUser.getLoginName()))
.andExpect(jsonPath("$.[0].slug").value(saveArticle1.getSlug()));
}
这里的测试的关键和之前的controller不同,之前只是使用了assertThat,这里使用到了mockito,要将MockMvc对象注入到容器中,之后就可以使用@MockBean来模拟repository层的对象
模拟repository的返回值,这个时候需要使用到mockito的when,thenReturn方法,当执行repository的某个方法的时候,就返回XXX数据,这里使用的注解为@WebMvcTest,所以发起请求不再是使用之前的一体化测试中的TestRestTemplate,而是使用mockMvc的perform方法,这个方法伴随着测试,就是配合accept方法确定响应的数据类型,同时使用andExcept来确定这里的响应的内容
响应的内容的断言,mockmvc中,类似于之前的assertThat【如果需要进行切入,那么就需要在test的Resource中配置,main中的切面不是,不需要这个配置】
$.[0] 代表的就是返回的Json对象集合,.[0]就是取出集合中的第一个元素,代表的就是响应的对象
eg:
.andExcept("$.[0].author.loginName").value(XXX)
【这里和之前的assertThat(xxx).isEqualTo(XXX)类似】
自动配置与外部配置
其实前面大部分的内容都是SpringBoot的测试…@SpringBootTest,@WebMvcTest,@DataJpaTest
SpringBoot的自动配置之前分享过,就是@AutoConfigruation会去扫描外部的starter遵守SPI规范创建的【服务发现机制】META-INF/factories,扫描将需要自动装配的对象放入到springBoot容器中即可
而外部配置则是使用properties、yml文件,结合注解@Value,@ConfigurationProperties,可以将配置注入程序 【比如在yml中配置的自定义数据就需要使用@Value才能在程序中使用】
配置启用的优先级最高的就是Devtools中的全局配置文件,.spring-boot-devtools.properties【要启用devtools,引入了devtools依赖】
【port的配置不一定是在yml中进行,在run/configuration中也可以图形化配置(博主不喜欢界面,比如boot-CLI好用多了)】
application.yml
配置文件的优先级: properties > yml > yaml
项目中使用h2嵌入式DB
为了快捷搭建demo,快速调试,可以使用h2嵌入式数据库,这里我们首先就直接使用IDEA的database模块,创建一个h2数据库
创建时,选择embed选项,代表嵌入,同时设置url路径为: jdbc:h2:~cfeng,这里就是在yml中配置的连接url,创建好之后,可以测试连接一下,测试连接不成功,那可能没有开启相关的environment,所以需要到Edit configuration中进行设置一下为MODULES …; 之后就可以了
但是需要注意: 如果在yml中设置为: jdbc:h2:~/cfengBase;MV_STORE=false
那么会报错,因为后面的选项是关闭了MV的STORE
- 之后就是在yml中进行配置
spring:
datasource:
url: jdbc:h2:~/cfengBase
driver-class-name: org.h2.Driver
username: cfeng
password: 123456
jpa:
hibernate:
ddl-auto: update
database: h2
show-sql: true
open-in-view: true #这个是为了进行视图显示,SpringBoot提示的Warn
h2:
console:
enabled: true
path: /h2-console
首先就是配置dataSource,这里的url就是刚刚创建的嵌入式h2,同时driver就是h2.Driver;之后就是设置jpa的参数,包括ddl-auto代表启动时的权限,还有展示show-sql和指定database为h2, 之后就是设置h2的参数,比如enabled就是启用,还有path设置路径,这里可以使用IDEA,所以可以不配置
- 创建repository对象,继承JpaRepository【CrudRepositoty】也行,可以不加@Repository,因为继承了之后默认会常见相关的Bean对象
public interface BlogUserRepository extends JpaRepository<BlogUser,Long> {
//通过登录名查找用户
BlogUser findUserByLoginName(String loginName);
}
- 创建相关的类操作repository对象
自动注入三种方式 final + @RequiredArgsConstrutor
- 首先就是常见的属性注入【其实就是对应xml配置文件中的】使用@Autowired加上@qulifier或者@Resource就可以
@Resource
private BlogUserRepository blogUserRepository;
缺点就是这里的bean是可以修改的,如果一个关键的大型项目,不小心设置为null,那么就无了
- Setter注入
set注入在xml中就是构建之后直接就会查找对应的set方法,如果使用注解的方式,就会显得比上面的方式繁琐一些
private BlogUserRepository blogUserRepository
public void setBlogUserRepository(BlogUserRepository blogUserRepository) {
this.blogUserRepository = blogUserRepository;
}
- 构造器注入,final + 构造器, springboot推荐的方式
private final BlogUserRepository blogUserRepository;
public BlogUserController(BlogUserRepository blogUserRepository) {
this.blogUserRepository = blogUserRepository;
}
当然这里可以借助Lombok插件简化,毕竟就是一个构造器
@RequiredArgsConstructor //配合final属性进行自动注入
public class BlogUserController {
private final BlogUserRepository blogUserRepository;
因为这里SpringBoot在发现其有属性之后,就会使用反射机制并且在容器中寻找对应的对象,通过构造方法注入;之前的@Autowired等类型注解就是直接找到对象赋值给属性,推荐使用构造器的方式,final修饰,不可修改
@ConfigurationProperties 配置文件映射
这个注解的作用就是实现配置文件的属性值映射为一个具体的对象,对于yml来讲,就是一级下面的所有的子属性都是该类的属性
可以加上@Component来将其变为对象放入spring容器,或者也可以通过在主启动类上面加上一个@EnableConfigurationProperties(BlogProperties.class), 创建其对象放入容器,思路和之前MapperScan等类似 //扫描需要进行配置文件属性注入的类
//@ConfigurationProperties要能够被主启动类发现,需要在主启动类上加上EnableXXX启用这个配置文件接收类
@ConfigurationProperties("blog")
响应中文乱码问题
server:
port: 8086
servlet:
context-path:
encoding:
charset: UTF-8
force: true
enabled: true
就和之前的原生的Servlet中直接设置contentType,还有后面直接配置一个Spring时代配置一个字符过滤器,到了更加easy的SpringBoot时代,直接配置servet.servlet.endcoding即可; 当然还是离不开Servlet
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL via JDBC Statement
-
出现这个问题,说明是表创建有问题,如果你使用的Mysql,可以检查一下方言,也就是SpringBoot配置中的datasource-plateform是否是正确的,不同的版本对应不同的方言
-
当然,h2不是这个问题,那么问题还可能就是表名或者类名使用了H2的关键字; 我的问题就在于之前的用户表名为User,而H2中就默认有User这个关键字,所以报错了,这个时候修改为BlogUser就可以成功创建了
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘articleRepository’: Invocation of init method failed;
- 出现这个问题,如果你的repository没有想用作repository,那么加一个NoRepoistoryBean注解即可,就不会创建bean
- 这里不关@Repository什么事【可加可不加】,因为继承了JpaRepository或者CrudRepository就会自动创建Bean; 问题主要在于全自动的JPA框架,要求自定义dao方法遵循JPA规范; 也就是说自定义的方法的名称是不能随意写的,因为JPA不需要写SQL,解析sql就是依靠JPA规范下的方法名称,比如findAllByOrderByAddedAtDesc; 这个名称就是不能改变的; -----> 这里的含义就是查询所有的对象按照时间降序排列 【我之前就是因为这里手快了方法名写错了一个字母报错的】
SpringBoot会自动扫描这些repository接口床架具体的实现对象放入容器中,就和之前扫描dao接口相同,但是这里不需要相关的Scan,而Mybatis中需要MapperScan
🎄程序出现异常的时候不要急忙去网上找答案,先分析一下异常的含义,出现同一个错误的原因不同,还是通过异常的提示信息直接定位来的轻松🎄
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)