Spring Data JPA(一) 基础入门
Spring Data JPA(一) 基础入门
JPA 的全称是 Java Persistence API , 即 Java 持久层 API。Spring Data JPA 是 Spring 生态中提出的一套数据库 ORM (对象关系映射)规范、抽象标准,或者说它是对ORM框架实现的顶层抽象封装,目的是整合不同的ORM技术以遵循统一的规范,并允许开发者以面向对象的方式实现数据库操作。其特点如下:
- 简化开发: 更好的无缝集成Spring生态,提供声明式的数据访问方式,以面向对象的概念实现数据库操作,以简化对数据库的CRUD操作;
- 统一规范: 现有的ORM框架技术(Hibernate、MyBatis等)如雨后春笋,其使用方法、特性架构等各不相同,这极大提高了开发者的开发门槛;Spring Data JPA 的目标是提供一套标准及接口,以统一不同的ORM技术,使得开发者无需关心底层ORM框架的实现细节,只需遵循JPA的标准接口开发即可;
- Hibernate: 现 JPA 的底层ORM框架基于的是全自动ORM框架Hibernate(MyBatis是半自动),期待以后会有更多的全自动ORM框架出现吧,以集成进Spring Data JPA;
一. SpringBoot 配置
1. 配置说明
配置项 | 说明 | 属性值 |
---|---|---|
spring.jpa.generate-ddl | 是否在SpringBoot项目启动时初始化库表(正向工程) | - true :SpringBoot项目在启动时将实体类映射并创建到数据库表中。注意: 只会检测新增字段映射,但不会删除旧字段/实体类中已经没有的字段;- false :默认值(不写该配置默认false ),不开启数据库表的正向工程;- 注意: 若通过此注解生成数据库表,则务必在 @Column 中指明每个字段的约束(columnDefinition :主键规则、非空、长度等,否则全部按默认值生成); |
spring.jpa.show-sql | 是否开启SQL打印 | - true :开启,注意开启后可能会降低性能;- false :默认值(不写该配置默认false ); |
spring.jpa.hibernate.naming.implicit-strategy | 逻辑名称映射策略(一般不用) | - ImplicitNamingStrategyJpaCompliantImpl :默认的命名策略,兼容JPA 2.0的规范,后者均继承自它;- ImplicitNamingStrategyLegacyHbmImpl :兼容Hibernate老版本中的命名规范;- ImplicitNamingStrategyLegacyJpaImpl :兼容JPA 1.0规范中的命名规范- ImplicitNamingStrategyComponentPathImpl :大部分与ImplicitNamingStrategyJpaCompliantImpl相同,但是它在逻辑属性名称中包含了复合名称; |
spring.jpa.hibernate.naming.physical-strategy | 物理名称映射策略 | - org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy :驼峰命名转下划线命名;- org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl :直接映射,不做处理(大小写都不变); |
2. 映射策略
Hibernate 5版本,引入了两种新策略来提供对命名规则的深度定制即 hibernate.naming.implicit-strategy
与 hibernate.naming.physical-strategy
,二者都是Hibernate数据库配置项中默认命名映射策略,是对数据库表和实体属性字段映射的默认处理方式。实体名称映射到数据库中时,其分为两个步骤:
- 第一个阶段:从对象模型中提取合适的逻辑名称,该逻辑名称可由用户指定,即通过@Column和@Table等注解完成(显式命名,非必要);也可以通过被Hibernate的
ImplicitNamingStrategy
指定,即表名隐式处理为类名,列名隐式处理为字段名(隐式命名,默认)。 - 第二个阶段:将上述提取的逻辑名称进一步解析成数据库对应字段物理名称,物理名称是由Hibernate中的
PhysicalNamingStrategy
决定;
PhysicalNamingStrategy
和ImplicitNamingStrategy
的区别如下,一般只用一种即可:
- 从处理的效果上来看:没有什么区别,但是从程序设计分层的角度来看,类似于MVC的分层,ImplicitNamingStrategy只管模型对象层次的处理,PhysicalNamingStrategy只管映射成真实的数据名称的处理,但是为了达到相同的效果,比如将userName映射成数据列时,在PhysicalNamingStrategy决定映射成user_name,但是在ImplicitNamingStrategy也可以做到。
- 从处理的场景来看:无论对象模型中是否显式地指定列名或者已经被隐式决定,PhysicalNamingStrategy都会被应用; 但是对于ImplicitNamingStrategy,仅仅只有当没有显式地提供名称时才会使用,也就是说当对象模型中已经指定了@Table或者@Entity、@Column等name时,设置的ImplicitNamingStrategy并不会起作用。
二. 基本使用流程
1. 类与库表映射
1.1 实体定义
(1)@Entity
标注在实体类上,Spring会扫描被@Entity标注的实体类并交由JPA管理,在程序运行时识别并映射到对应的数据库表。
属性 | 含义 | 示例 |
---|---|---|
name | 指定实体类名称,默认为当前实体类的非限定类名。注意此处不是直接映射到数据库表名。 | @Entity(name=“XXX”) 对应的JQPL中的实体类名是XXX。 |
注意: 经测试,不写Getter/Setter方法对JPA生成无影响,但在实际工作中为了代码可读性最好还是加上@Data。
(2)@Table
标注在实体类上,@Table与@Entity并列使用,用于指定实体类映射的数据库表名。默认不使用@Table情况下,JPA将通过配置的 hibernate.naming.physical-strategy
自动映射策略来将类名映射到数据库表。
属性 | 含义 | 示例 |
---|---|---|
name | 指定表名 | @Entity @Table(name=“order_info”) 对应的表名是order_info。 |
indexes | Index数组类型,指定表的索引,Index属性如下: - name:索引的名称 - columnList:参与索引的列名,多字段用’,'分隔 - unique:是否是唯一索引,默认false | @Entity @Table(name = “order_info”, indexes = { @Index(name = “idx_c”, columnList = “c”), @Index(name = “idx_a_b”, columnList = “a, b”, unique = true) }) |
1.2 主键定义
(1)@Id
标注在属性上,@Id 用于标记实体类中的属性映射为数据库主键(显式主键),该注解无特定属性值。注意: 实体必须声明主键@Id,但可以不声明生成策略 @GeneratedValue(无策略则新增数据必须持有主键)。
(2)@GeneratedValue
标注在属性上,@GeneratedValue 配合 @Id 一同使用,用于通过 strategy 属性值指定主键的生成策略。常用策略如下:
策略 | 含义 |
---|---|
GenerationType.IDENTITY | 主键由数据库自动生成,主键自动增长型,其中Mysql支持,Oracle 不支持这种方式 |
GenerationType.AUTO | 默认主键生成策略,由JPA根据数据库类型自动选择合适的主键生成策略(Oracle 默认是序列化的方式,Mysql 默认是主键自增的方式) |
GenerationType.SEQUENCE | 根据底层数据库的序列来生成主键,条件是数据库支持序列,其中 Oracle支持,Mysql不支持 |
1.3 属性映射
(1)@Column
标注在属性上,@Column 用于指定实体属性与数据库表列的映射关系,并配置数据库字段的具体特征;默认不使用任何注解的属性会基于属性名自动通过配置的 hibernate.naming.physical-strategy
全局策略映射到数据库字段上。
属性 | 含义 |
---|---|
name | 指定映射到数据库表中的字段名 |
unique | 指定是否唯一,默认为false |
nullable | 指定是否允许为null,默认为true |
insertable | 指定是否允许插入,默认为true |
updatetable | 指定是否允许更新,默认为true |
length | 指定字段存储的最大字符长度,仅对String类型(VARCHAR)的字段有效,默认值为255 |
columnDefinition | 在创建表时,指定该字段创建的SQL语句,一般用于通过Entity生成表定义时使用,例如:@Column(columnDefinition = “VARCHAR(255) NOT NULL”);也就是说,如果DB中表已建好,则该属性没有必要使用 |
(2)@Enumerated
标注在属性上,@Enumerated 只能用于枚举类型的属性上,用于通过 value 属性值指定枚举类型的数据库存储方式。
存储方式 | 含义 |
---|---|
EnumType.ORDINAL | 将枚举值映射为整数(通常是枚举声明的顺序,从0开始),这是默认选项 |
EnumType.STRING | 将枚举值映射为其名称(即枚举常量的字符串表示) |
注意: 当使用 EnumType.ORDINAL
时,如果枚举的顺序发生变化(例如,在枚举中插入或删除枚举常量),则可能会导致数据不一致或查询错误。因此,在枚举常量可能发生变化的情况下,建议使用 EnumType.STRING
。
(3)@Transient
用于属性上,@Transient 用于标记不映射到数据库表的属性。
1.4 映射规则
Spring Data JPA 按照实体类中定义的映射规则生成执行SQL,其生成格式如下:
select demoinfo0_.id as id1_0_, demoinfo0_.name as name2_0_ from demo_info demoinfo0_
insert into demo_info (name, id,......) values (?, ?,.....)
(1)映射匹配性
- 数据库字段多于实体类属性时: 数据操作可以正常读写,只不过实体类中缺少映射的字段在数据库中为空,注意数据库中对应字段约束须允许为空才行;
- 数据库字段与实体类属性存在不匹配或数据库字段少于实体类属性:
findAll
、save
等系列方法都会报错,提示存在字段无法映射(手动选定操作字段也不行);- 解释如下:“即使你在查询时没有显式地使用不匹配列,Hibernate 仍然会在加载实体对象时尝试映射所有定义在实体类中的字段。如果实体类中定义了一个字段,但在数据库表中不存在对应的列,Hibernate 会在执行查询时抛出 SQLGrammarException”,因此如果实体类中存在不映射的字段,应该使用@Transient标注才会避免在CRUD中出现未知错误。
(2)默认映射关系
在默认情况下,JPA按照驼峰命名法对属性名称/实体名称进行拆分(小写形式),并使用“_”下划线命名转换。驼峰命名法(Camel Case) 是一种常用的命名约定,主要用于变量、方法和类的命名。它有两种主要形式:
- 小驼峰命名法(lower camel case):第一个单词首字母小写,后续单词首字母大写。例如:firstName, lastName, userAddress
- 大驼峰命名法(upper camel case):所有单词首字母大写。例如:FirstName, LastName, UserAddress
(3)数据类型选择
在 Spring Data JPA 中,实体类的属性数据类型可以是基本数据类型(如 int,long,boolean 等),也可以是对应的包装类(如 Integer,Long,Boolean 等);选择哪种类型取决于你的具体需求和偏好:
- 基本数据类型: 内存占用小,但不能表示 null 值,如果数据库中的某个字段允许为 null,则使用基本数据类型会导致插入或读取数据时出现问题。
- 包装类型: 可以表示 null 值,适用于数据库中允许 null 的字段,包装类为null插入不允许为null的表字段会报错(有默认值除外),但性能稍差(需要装箱/拆箱操作)。
(4)@Column 的属性配置
- 初始化建表时:数据库表会根据配置属性值建立物理表规则,比如 nullable、length 等;
- 已有表但属性值与表不匹配时:按照实体类中的约束进行校验,哪怕是数据库表中没有约束(比如允许为null),但实体类中存在约束(不允许为null),则按照实体类中约束进行判断通过后再映射执行SQL(ConvertException);
- insertable与updatable行为:当该属性值为true时,则在执行insert和update语句时,生成的sql中不会包含该属性字段;该属性作用于生成SQL之前,nullable等作用于执行SQL时对字段的校验,因此当该属性与@Column其他属性产生冲突时,会先通过该属性过滤掉nullable的字段,nullable此时不会生效判断;
2. CRUD 数据操作
Spring Data JPA 通过 Repository 接口来实现数据持久化的 CRUD 操作,其中泛型T
表示数据库映射的实体类、泛型ID
表示主键的数据类型。Repository 接口的继承关系树如下:
- Repository: Spring Data JPA 用于标识数据库抽象的顶层接口,聚合对实体的 CRUD;
- CrudRepository: 继承了 Repository 接口的子接口,定义了基本的 CRUD 方法(比如保存、更新、查询、删除);
- PagingAndSortingRepository: 继承自 CrudRepository 接口的子接口,除了具有CrudRepository基本功能外,还定义了基本的分页和排序查询方法;
- QueryByExampleExecutor: 支持基于实例的动态查询的方法、允许通过传递一个示例对象 Example 来执行查询,而不需要手动编写查询语句;
- JpaRepository: 同时继承了PagingAndSortingRepository和QueryByExampleExecutor的子接口,聚合功能的同时,对某些操作进行了拓展并将其他接口的方法返回值做了适配处理(List);
Spring Data JPA 通过 Repository 来简化数据库的持久化操作,大多数情况下我们只需要定义继承 JpaRepository 的Dao层子接口,指明其需要关联操作的实体类型和主键类型(泛型),就可以实现对数据库表的操作,甚至不需要提供任何注解(比如MyBatis的@Mapper);Spring 容器在启动时默认会扫描项目中的所有 Repository 接口,并将其注册到 Spring 容器中管理,并通过动态代理和AOP解析执行方法、生成SQL语句,使用 EntityManager 来执行数据库操作。Spring Data JPA 支持通过保留方法、约定方法命名规则的方式实现不写SQL即可操作数据,也支持通过原生SQL、JPQL的方式实现数据操作,接下来我们将分别介绍。
public interface IDemoInfoDao extends JpaRepository<DemoInfo, Long> {//crud
}
2.1 使用保留方法
Spring Data JPA 的 Repository 接口家族中预定义了通用场景下的基本 CRUD 方法,对于一些简单的数据库操作,我们的Dao层子接口在继承 JpaRepository 接口后,甚至不用添加任何其他方法就可以实现开箱即用。本质上,这些接口都是由默认实现类 SimpleJpaRepository
实现的,可以到该类下查看保留方法默认实现源码。常用的基本保留方法如下:
Return | Method | Description |
---|---|---|
List | findAll() | Returns all instances of the type T . |
List | findAllById(Iterable<ID> ids) | Returns all instances of the type T with the given IDs . If some or all ids are not found, no entities are returned for these IDs (results unordered). |
Optional | findById(ID id) | Retrieves an entity by its id . |
save(S entity) | Saves a given entity . Return the saved entity. | |
saveAndFlush(S entity) | Saves an entity and flushes changes instantly. Return the saved entity. | |
saveAll(Iterable<S> entities) | Saves all given entities . Return the saved entities. | |
saveAllAndFlush(Iterable<S> entities) | Saves all entities and flushes changes instantly. Return the saved entities. | |
void | deleteById(ID id) | Deletes the entity with the given id . It is ignored if not found. |
void | delete(T entity) | Deletes a given entity . |
void | deleteAll() | Deletes all entities managed by the repository. |
void | deleteAllById(Iterable<? extends ID> ids) | Deletes all instances of the type T with the given IDs . Entities that not found will be ignored. |
void | deleteAll(Iterable<? extends T> entities) | Deletes the given entities . |
void | deleteAllInBatch() | Deletes all entities managed by the repository in a batch call. |
void | deleteAllByIdInBatch(Iterable<ID> ids) | Deletes the entities identified by the given ids using a single query. |
void | deleteAllInBatch(Iterable<T> entities) | Deletes the given entities in a batch which means it will create a single query. |
2.1.1 find 系列
// Hibernate: select demoinfo0_.id as id1_0_, demoinfo0_.des_info as des_info2_0_, demoinfo0_.name as name3_0_ from demo_info demoinfo0_
List<DemoInfo> demoAllList = demoInfoDao.findAll();
System.out.println(demoAllList);// Hibernate: select demoinfo0_.id as id1_0_, demoinfo0_.des_info as des_info2_0_, demoinfo0_.name as name3_0_ from demo_info demoinfo0_ where demoinfo0_.id in (? , ? , ? , ?)
List<DemoInfo> demoAllListById = demoInfoDao.findAllById(Arrays.asList(1L,2L,3L,5L));
System.out.println(demoAllListById);// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
Optional<DemoInfo> demoById = demoInfoDao.findById(4L);
System.out.println(demoById.get());
2.1.2 save 系列
(1)save(S entity) 与 saveAll(Iterable entities)
//1、entity传递主键id,数据库对应主键数据不存在
// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
// Hibernate: insert into demo_info (des_info, name, id) values (?, ?, ?)
demoInfoDao.save(new DemoInfo(2L, "jerry", "save test"));//2、entity传递主键id,数据库对应主键数据存在,但数据未更改
// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
demoInfoDao.save(new DemoInfo(2L, "jerry", "save test"));//3、entity传递主键id,数据库对应主键数据存在,且数据已更改
// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
// Hibernate: update demo_info set des_info=?, name=? where id=?
demoInfoDao.save(new DemoInfo(2L, "jerry2", "save test"));//4、entity不传递主键id(null),未配置主键生成策略 GeneratedValue
// IdentifierGenerationException: ids for this class must be manually assigned before calling save()
demoInfoDao.save(new DemoInfo(null, "jack", "save test"));//5、entity不传递主键id(null),配置主键生成策略 GeneratedValue(数据库同时配置)
// Hibernate: insert into demo_info (des_info, name) values (?, ?)
demoInfoDao.save(new DemoInfo(null, "jack", "save test"));//6、saveAll 批量插入
// (1)主键+存在数据更改:select+update
// (2)主键+不存在数据更改:select
// (3)无主键插入:insert
//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: insert into demo_info (des_info, name) values (?, ?)
//Hibernate: update demo_info set des_info=?, name=? where id=?
demoInfoDao.saveAll(Arrays.asList(new DemoInfo(3L, "tom", "gogo"), new DemoInfo(4L, "jack", "save test"),new DemoInfo(null, "lisa", "save test test")));List<DemoInfo> demoAllList = demoInfoDao.findAll();
System.out.println(demoAllList);
由上可以看出,save
方法同时用于实现数据插入(insert)和数据更新(update)操作,其判断标准为entity
对象是否已存在,而saveAll
方法的实现则对entities
进行遍历,并逐个调用save
方法保存。逻辑如下:
- 若保存
entity
传递了主键:先通过select查找数据实例是否存在:- 若存在:判断数据是否更新,若发生了修改则执行update更新操作;
- 若不存在:执行insert插入操作;
- 若保存
entity
未传递主键:直接执行insert插入操作(主键必须配置生成策略);
我们来进一步看一下save(S entity)
与 saveAll(Iterable entities)
在 SimpleJpaRepository
中的实现源码,save(S entity)
与 saveAll(Iterable entities)
本质上也是属于同根同源:
// 默认事务
@Transactional
public <S extends T> S save(S entity) {Assert.notNull(entity, "Entity must not be null.");if (this.entityInformation.isNew(entity)) {this.em.persist(entity);return entity;} else {return this.em.merge(entity);}
}
// 默认事务
@Transactional
public <S extends T> List<S> saveAll(Iterable<S> entities) {Assert.notNull(entities, "Entities must not be null!");List<S> result = new ArrayList();Iterator var3 = entities.iterator();while(var3.hasNext()) {S entity = var3.next();result.add(this.save(entity));}return result;
}
(2)saveAndFlush(S entity) 与 saveAllAndFlush(Iterable entities)
saveAndFlush(S entity)
与 saveAllAndFlush(Iterable entities)
在使用效果上跟普通的save
方法基本一致,那么我们接着来看一下 saveAndFlush(S entity)
与 saveAllAndFlush(Iterable entities)
的源码如下:
// 默认事务
@Transactional
public <S extends T> S saveAndFlush(S entity) {S result = this.save(entity);this.flush();return result;
}
// 默认事务
@Transactional
public <S extends T> List<S> saveAllAndFlush(Iterable<S> entities) {List<S> result = this.saveAll(entities);this.flush();return result;
}
可以看出,saveAndFlush(S entity)
与 saveAllAndFlush(Iterable entities)
在实现中也是直接调用了save(S entity)
与 saveAll(Iterable entities)
,只是在执行完成后主动调用了EntityManager
的flush
方法。flush
的作用是直接将执行的sql发送至数据库服务器,否则执行语句将被暂存在 JPA 持久化上下文中,需等到事务提交的时才会真正发送执行SQL语句(第四节原理部分会进行分析)。
2.1.3 delete 系列
Spring JPA Ⅲ delete方法详解 :需要注意的是 delete 系列保留方法上也均添加了默认事务 @Transactional
(1)delete(T entity) 与 deleteById(ID id)
// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
// Hibernate: delete from demo_info where id=?
demoInfoDao.deleteById(2L);// Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
// Hibernate: delete from demo_info where id=?
// 注意:通过实体 entity 主键 id 进行删除操作,只设置主键即可
demoInfoDao.delete(new DemoInfo(4L,null,null));
delete(T entity)
与 deleteById(ID id)
方法同根同源,二者都是先通过主键查询(select)实体 entity ,然后基于查询结果执行删除操作(delete)。区别在于:
delete
:若存在则删除,否则直接返回;deleteById
:若存在则调用delete
方法删除,否则抛出异常EmptyResultDataAccessException
(此时删除操作不会提交到数据库);
(2)deleteAll()、deleteAll(Iterable entities)与deleteAllById(Iterable ids)
//Hibernate: select demoinfo0_.id as id1_0_, demoinfo0_.des_info as des_info2_0_, demoinfo0_.name as name3_0_ from demo_info demoinfo0_
//Hibernate: delete from demo_info where id=?
//Hibernate: delete from demo_info where id=?
//Hibernate: delete from demo_info where id=?
demoInfoDao.deleteAll();//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: delete from demo_info where id=?
//Hibernate: delete from demo_info where id=?
demoInfoDao.deleteAll(Arrays.asList(new DemoInfo(1L,"name_test","des_test"), new DemoInfo(3L,"name_test","des_test")));//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//Hibernate: select demoinfo0_.id as id1_0_0_, demoinfo0_.des_info as des_info2_0_0_, demoinfo0_.name as name3_0_0_ from demo_info demoinfo0_ where demoinfo0_.id=?
//EmptyResultDataAccessException: No class com.study.springdatajpademo.pojo.DemoInfo entity with id 5 exists!
demoInfoDao.deleteAllById(Arrays.asList(1L,2L,5L));List<DemoInfo> demoAllList = demoInfoDao.findAll();
System.out.println(demoAllList);
deleteAll()
、deleteAll(Iterable entities)
与deleteAllById(Iterable ids)
方法都不是批量删除,在实现中三者都是先执行查询(select),然后遍历查询结果逐条执行删除操作(delete),因此大数据量下的删除效率并不高。三者区别如下:
deleteAll()
:通过findAll
求出所有实体对象,然后遍历循环调用delete(entity)
方法;deleteAll(Iterable entities)
:遍历 entities 列表,然后循环调用delete(T entity)
方法(逐条查询并删除);deleteAllById(Iterable ids)
:遍历 ids 列表,然后循环调用deleteById(Id id)
方法(逐条查询并删除),注意对于不存在的id会抛出异常EmptyResultDataAccessException
(此时所有的删除操作不会提交到数据库);
(3)deleteAllInBatch()、deleteAllInBatch(Iterable entities)与deleteAllByIdInBatch(Iterable ids)
// Hibernate: delete from demo_info
demoInfoDao.deleteAllInBatch();// Hibernate: delete from demo_info where id=? or id=?
demoInfoDao.deleteAllInBatch(Arrays.asList(new DemoInfo(1L,"name_test","des_test"), new DemoInfo(3L,"name_test","des_test")));// Hibernate: delete from demo_info where id in (? , ? , ?)
demoInfoDao.deleteAllByIdInBatch(Arrays.asList(1L,2L,5L));List<DemoInfo> demoAllList = demoInfoDao.findAll();
System.out.println(demoAllList);
可以看出InBatch
系列的方法不再是逐条删除,而是批量删除,并且不再执行查询语句,在大数据量下的执行效率更高。在实现上,deleteAllInBatch
和deleteAllByIdInBatch
都是遍历entities或ids,对执行SQL字符串进行了拼接,使用or
以及in
关键字实现数据记录定位,而不再依靠find
方法。
2.2 基于方法命名规则
除了上述已定义好的保留方法外,Spring Data JPA 还支持通过方法命名规则来创建自定义数据库操作,而无需声明任何SQL语句;我们只需要在 Respository 接口中按照方法命名规则创建数据库执行接口方法,程序在执行时会自动对符合规则的方法名进行关键词解析和映射,从而创建数据库执行操作。常用的执行关键字以及逻辑连接词分别如下:
Execute Keyword | Description |
---|---|
find…By , read…By , get…By , query…By , search…By , stream…By | General query method returning typically the repository type, a Collection or Streamable subtype or a result wrapper such as Page , GeoResults or any other store-specific result wrapper. Can be used as findBy… , findMyDomainTypeBy… or in combination with additional keywords. |
exists…By | Exists projection, returning typically a boolean result. |
count…By | Count projection returning a numeric result. |
delete…By , remove…By | Delete query method returning either no result (void ) or the delete count(int ). Warn:need to display declaration transactions |
…First<number>… , …Top<number>… | Limit the query results to the first <number> of results. This keyword can occur in any place of the subject between find (and the other keywords) and by . |
…Distinct… | Use a distinct query to return only unique results. Consult the store-specific documentation whether that feature is supported. This keyword can occur in any place of the subject between find (and the other keywords) and by . |
Logic Keyword | Sample | JPQL snippet |
---|---|---|
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is , Equals | findByFirstname ,findByFirstnameIs ,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull , Null | findByAge(Is)Null | … where x.age is null |
IsNotNull , NotNull | findByAge(Is)NotNull | … where x.age is not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (参数需加前缀 % ) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (参数需加后缀 % ) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (参数需被 % 包裹) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
通过组合这些关键词来定义方法名就能实现数据库交互在一定程度上降低了开发者的使用门槛,这也是Sprng Data JPA的初衷和特点之一。关于这部分,我们将会在第四节中进一步分析方法名规则解析与构造的源码实现,需要注意的是在实际使用该方式时:
-
实体类属性名称对应: 命名规则定义方法中声明的查询属性名称必须与实体类 Entity 中的类属性名称对应,而非数据库字段名或@Column映射名;
-
小驼峰方式命名: 实体类中定义的类属性名称最好是以小写字符开始(JPA会自动将识别属性首字符转换为小写进行映射,但若是连续两个大写字母则不做处理),按照小驼峰方式命名;
-
声明顺序一致: 命名规则方法在绑定查询参数时默认使用方法形参位置占位,因此方法参数的个数、顺序及数据类型均需要与定义的方法声明顺序一致;
2.3 引入SQL与JPQL
2.3.1 查询操作
(1)SQL与JPQL
JPA 同样支持通过直接数据库操作语句来操作数据,在 JPA 中数据库操作语句有两种表现形式:
-
JPQL:JPQL(Java Persistence Query Language)是 JPA 提供的一种面向对象的查询语言,其语法类似于 SQL,但它操作的是实体对象而不是数据库表,更加符合面向对象编程的理念,并且具有更好的跨数据库可移植性;
SELECT <实体别名> FROM <实体类名> <实体别名> WHERE <条件表达式包含命名参数>
-
SQL:直接编写数据库特定的原生 SQL 语句,其依赖于特定数据库的 SQL 语法(后文将以SQL为主进行展开);
SELECT <列名1>, <列名2> FROM <表名> WHERE <条件表达式包含命名参数>
在JPA中要使用直接数据库操作语句进行查询,我们只需在 Repository 自定义接口方法上声明 @Query 注解即可,JPQL 和 SQL 的声明方法区别如下:
// JPQL
@Query("select u FROM User u where u.name = ?1 and u.age > ?2")
List<User> findByNameAndAge(String name, int age);
// SQL
@Query("select * from user where user_name = ?1 and user_age > ?2", nativeQuery = true)
List<User> findByNameAndAge(String name, int age);
(2)参数位置绑定
在 @Query 中进行方法参数绑定时,支持通过索引参数绑定和命名参数绑定两种方式:
- 索引参数绑定:
?index
指定占位符index
,需要方法传递参数的个数与顺序均保持一致; - 命名参数绑定:
:paramName
指定@Param(paramName)
注解中的参数别名paramName
,不要求顺序;
// 索引参数绑定
@Query("select * from user where user_name = ?1 and user_age > ?2", nativeQuery = true)
List<User> findByNameAndAge(String name, int age);// 命名参数绑定
@Query("select * from user where user_name = :name and user_age > :age", nativeQuery = true)
List<User> findByNameAndAge(@Param("name") String name, @Param("age") int age);
(3)部分查询
在Spring Data JPA 查询场景下,我们有时只需查询部分字段而非整个实体,尤其在数据库非常复杂或数据库字段较多的时候;但是这种情况下,直接使用实体接收来部分字段会产生接收异常,目前有两种常用方式如下:
- 使用容器Map接收
// 查询单数据记录
@Query(value = "SELECT name,des_info FROM demo_info WHERE id = ?1",nativeQuery = true)
public Map<String, Object> findRawMapByObject(Long id);// 查询多数据记录
@Query(value = "SELECT name,des_info FROM demo_info WHERE id >= ?1",nativeQuery = true)
List<Map<String, Object>> findRawMapByList(Long id);
- 使用JPQL与投影(Projections)
Projections :: Spring Data JPA
投影(Projections)是指实体在部分属性字段上查询结果的聚合,其实现方式包括基于接口的投影(Interface-based Projections)和基于类的投影(Class-based Projections ,DTOs)。本节以基于类的投影为主,投影类的使用方式与接口投影完全相同,只是不会发生代理,也不能用嵌套投影。
在投影类实现中需要包含目标查询字段,并由公开的构造方法的参数名确定。在 JPQL 中,处理 DTO 的方法是使用 new
关键字来调用 DTO 的构造函数,JPQL 会将查询的结果映射到 DTO 对象中。其使用方法如下:
// User 实体的 DTO 投影类
@Data
public class UserDTO implements Serializable{private String dtoName;private int dtoAge;public UserDto(String dtoName, int dtoAge) {this.dtoName = dtoName;this.dtoAge = dtoAge;}// Getters and Setters
}// Repository
public interface UserRepository extends JpaRepository<User, Long> {// 需要指定全限定类名的初始化构造方法@Query("SELECT new com.example.demo.UserDTO(u.name, u.age) FROM User u WHERE u.id > :id")List<UserDTO> findUserDtoByList(@Param("id") Long id);
}
需要注意的是:
- 在 JPQL 中需要使用
new
关键字指定全限定类名的初始化构造方法,并将查询结果传递给构造函数; - 投影类(DTO)的构造函数需要匹配查询结果中字段的类型和顺序,投影类属性名称无需一致;
2.3.2 数据修改操作
Spring Data JPA 提供了 @Query 注解实现通过 JPQL 或原生 SQL 来执行数据库查询操作,而如果需要修改数据而非查询数据,则需要再结合 @Modifying 注解来标识该方法属于数据修改类操作,否则 JPA 将无法识别并抛出异常SQLException: Can not issue data manipulation statements with executeQuery()
,即无法使用Query
的方式发出DML操作。需要注意的是:
- @Modifying 注解的作用仅是与 @Query 注解结合使用来实现数据更新与修改,而JPA保留方法或基于命名规则的方法完全由JPA接管从而不需要此注解;
- @Modifying 注解标识的数据修改类方法的返回值应该是
void
或int
(表示修改语句所影响的数据记录数); - @Modifying 注解支持更新
UPDATE
及删除DELETE
操作。除此之外,在JPQL场景中不支持插入INSERT
操作( JPQL 语句不支持INSERT
,数据保存将由save
完成),而在SQL场景中是支持INSERT
操作的; - @Modifying 注解方法在调用时必须显式声明事务,否则将抛出
TransactionRequiredException: Executing an update/delete query
的异常(Spring Data JPA 默认是只读事务),事务可以在 Service 层的方法或直接在注解方法上添加;
// Hibernate: update demo_info set des_info = ? where id = ?
@Transactional
@Modifying
@Query(value = "update demo_info set des_info = ?1 where id = ?2", nativeQuery = true)
int updateDemoInfoById(String des, Long id);// Hibernate: insert into demo_info(name,des_info) values(?, ?)
@Transactional
@Modifying
@Query(value = "insert into demo_info(name,des_info) values(?1, ?2)", nativeQuery = true)
int insertDemoInfo(String name, String desInfo);
相关文章:
Spring Data JPA(一) 基础入门
Spring Data JPA(一) 基础入门 JPA 的全称是 Java Persistence API , 即 Java 持久层 API。Spring Data JPA 是 Spring 生态中提出的一套数据库 ORM (对象关系映射)规范、抽象标准,或者说它是对ORM框架实现的顶层抽象…...
Flutter | 基于函数式编程的通用单选列表设计
背景 项目中多次用到如下图的通用单选列表页: 常规封装 此列表需要三样东西: 标题数组当前选中项的 index点击 cell 的回调 封装大体如下: import package:flutter/material.dart;class ListPage1 extends StatefulWidget {const ListPa…...
华三防火墙F1000-AK系列策略路由配置案例(WEB)
1 配置需求或说明 1.1 适用的产品系列 本案例适用于如F1000-AK180、F1000-AK170等F1000-AK系列的防火墙。 1.2 配置需求及实现的效果 防火墙作为网络出口设备,外网有移动和联通两条线路。内网有192.168.1.0和192.168.2.0两个网段,需要实现192.168.1.0网段走移动线路,192…...
Oracle 锁表的解决方法及避免锁表问题的最佳实践
背景介绍 在 Oracle 数据库中,锁表或锁超时相信大家都不陌生,是一个常见的问题,尤其是在执行 DML(数据操作语言)语句时。当一个会话对表或行进行锁定但未提交事务时,其他会话可能会因为等待锁资源而出现超…...
深度学习中的生成对抗网络(GAN)原理与应用
引言 生成对抗网络(Generative Adversarial Network,简称GAN)是由Ian Goodfellow等人在2014年提出的一种深度学习模型,它通过对抗训练的方式生成与真实数据分布相似的假数据。GAN的出现极大地推动了深度学习和生成模型的研究&…...
Swing中JScrollPane面板
一、介绍 在设置界面时,可能会遇到在一个较小的容器窗体中显示一个较大部分的内容的情况,这时可使用JScrollPane面板。JScrollPane面板是带滚动条的面板,是一种容器,但是JScrollPane只能放置一个组件,并且不可使用布局…...
【学习笔记】检测基于RTOS的设计中的堆栈溢出-第2部分
有许多技术可用于检测堆栈溢出。有些使用硬件,而有些则完全在软件中执行。正如我们很快将看到的那样,在硬件中具有这种能力到目前为止是更可取的,因为堆栈溢出可以在发生时立即检测到,事实上,可以避免,因为硬件实际上可以防止对无效访问的写入。 硬件堆栈溢出检测机制通…...
PHP 函数
在php中有非常多的函数,函数这种东西不需要记全,直到怎么使用就行了,如果想了解多点函数,可以查看php官方函数手册,或者参考菜鸟PHP 5 Array 函数 | 菜鸟教程。 创建 PHP 函数 通常函数创建完毕后是用来调用。 语法格…...
centos更换源文件,换源,替换源
期初怎么折腾就是不行,换了源也是不能使用的,最后发现不是换的源不行,而是之前的源文件不行,然后给所有的源文件在yum源统一放在了bak目录下,随后我们再去下载安装源文件。 您将yum源下载之后,先将您的其他…...
【深度学习】四大图像分类网络之VGGNet
2014年,牛津大学计算机视觉组(Visual Geometry Group)和Google DeepMind公司一起研发了新的卷积神经网络,并命名为VGGNet。VGGNet是比AlexNet更深的深度卷积神经网络,该模型获得了2014年ILSVRC竞赛的第二名,…...
线性表-链式描述(C++)
链式实现的线性表: 链式实现的线性表,即链表(Linked List),是一种通过节点(Node)的集合来存储数据的线性数据结构。在链表中,每个节点包含两部分:存储数据的域ÿ…...
C++高阶算法[汇总]
(一)高精度算法概述 高精度算法是指能够处理超出常规数据类型表示范围的数值的算法。在 C 中,标准数据类型通常有固定的位数和精度限制,而高精度算法可以解决大数运算、金融计算和科学计算等领域的问题。 (二&#x…...
机器学习之DeepMind推出的DreamerV3
开放域任务强化学习(Open-Ended Task Reinforcement Learning)的目标是使智能体能够在多样化且未见过的任务中表现出色,同时能够实现任务间的迁移学习。这类研究的重点在于开发通用的学习算法,能够在没有明确任务定义的情况下,从环境中学习并推广到新任务。DeepMind的Drea…...
【Zookeeper】四,Zookeeper节点类型、通知、仲裁、会话
文章目录 Zookeeper的架构znode的版本Zookeeper的节点类型层级树状结构znode的不同类型 Zookeeper监视与通知通知的类型 Zookeeper的仲裁Zk的会话会话的生命周期 Zookeeper的架构 Zookeeper的服务器端运行两种模式:独立模式(standalone)和仲…...
Vue 集成和使用 SQLite 的完整指东
1. 引言 SQLite 是一种轻量级的关系型数据库管理系统,以其简单易用、无需服务器等特点广泛应用于嵌入式系统、移动应用和小型应用程序中。在 Web 开发中,尤其是前端应用开发中,SQLite 可以作为客户端本地存储的一种选择,为用户提…...
CMAKE常用命令详解
NDK List基本用法 Get–获取列表中指定索引的元素 list(Get list_name index output_var)解释 list_name: 要操作集合的名称index: 要取得的元素下标output_var: 保存从集合中取得元素的结果 栗子 list(GET mylist 0 first_element) # 获取第一个元素APPEND–在列表末尾…...
【嵌入式——QT】QT制作安装包
第一步 QT程序写好之后,编译release版本 第二步 拿到release生成的.exe文件 第三步 新建文件夹deploy 第四步 将.exe文件复制到deploy目录下 第五步 在该目录下输入cmd指令,回车 第六步 在打开的命令窗口下输入 windeployqt TegNetCom_1.0.…...
JavaScript 前端开发:从入门到精通的奇幻之旅
目录 一、引言 二、JavaScript 基础 (一)变量与数据类型 (二)运算符 (三)控制结构 三、函数 (一)函数定义与调用 (二)函数作用域 (三&am…...
shell编程基础笔记
目录 echo改字体颜色和字体背景颜色 bash基本功能: 运行方式:推荐使用第二种方法 变量类型 字符串处理: 条件判断:(使用echo $?来判断条件结果,0为true,1为false) 条件语句&a…...
FPGA实现GTP光口视频转USB3.0传输,基于FT601+Aurora 8b/10b编解码架构,提供3套工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案本博已有的FPGA驱动USB通信方案 3、工程详细设计方案工程设计原理框图输入Sensor之-->OV5640摄像头输入Sensor之-->芯片解码的…...
使用 PDF API 合并 PDF 文件
内容来源: 如何在 Mac 上合并 PDF 文件 1. 注册与认证 您可以注册一个免费的 ComPDFKit API 帐户,该帐户允许您在 30 天内免费无限制地处理 1,000 多个文档。 ComPDFKit API 使用 JSON Web Tokens 方法进行安全身份验证。从控制面板获取您的公钥和密钥&…...
Jenkins Nginx Vue项目自动化部署
目录 一、环境准备 1.1 Jenkins搭建 1.2 NVM和Nodejs安装 1.3 Nginx安装 二、Jenkins配置 2.1 相关插件安装 2.2 全局工具安装 2.3 环境变量配置 2.4 邮箱配置(构建后发送邮件) 2.5 任务配置 三、Nginx配置 3.1 配置路由转发 四、部署项目 …...
python代码实现问卷星问卷内容获取并写入word文档保存
以下附完整代码。 import os from tkinter import Tk, simpledialog, messagebox from docx import Document import time import requests import re from tkinter import ttk# 使用 tkinter 创建一个简单的输入框来获取用户输入的问卷链接 root Tk() root.title("问卷…...
C# 2024年Visual Studio实用插件集合
在2024年,Visual Studio作为.NET开发者的首选IDE,其插件生态不断壮大,为开发者提供了更高效、便捷的开发体验。本文将介绍一些实用的Visual Studio插件,特别是针对C#开发者,帮助提升开发效率和代码质量。 1. GitHub C…...
基于PHP的香水销售系统的设计与实现
摘 要 时代科技高速发展的背后,也带动了经济的增加,人们对生活质量的要求也不断提高。香水作为一款在人际交往过程中,给对方留下良好地第一印象的产品,在生活中也可以独自享受其为生活带来的点缀。目前香水市场体量庞大ÿ…...
QT去除窗口边框(无边框)
ch21_TencentMeetingLogin::ch21_TencentMeetingLogin(QWidget *parent): QDialog(parent) {ui.setupUi(this);this->setWindowFlags(Qt::FramelessWindowHint);//去除窗口边框 } 但此时窗口不能拖动且点击任务栏程序图标不能最小化! this->setWindowFlags(Q…...
【k8s】监控metrics-server
metrics-server介绍 Metrics Server是一个集群范围的资源使用情况的数据聚合器。作为一个应用部署在集群中。Metric server从每个节点上KubeletAPI收集指标,通过Kubernetes聚合器注册在Master APIServer中。为集群提供Node、Pods资源利用率指标。 就像Linux 系统一样…...
「Qt Widget中文示例指南」如何为窗口实现流程布局?(二)
Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。 本文将展示如何为不…...
EditInPlace就地编辑:Dom vs Form
利用Dom和Form实现就地编辑(EditInPlace)功能,两者在实现方式、用户体验和适用场景上有一些区别。下面我将详细解释这些区别: 1. EditInPlace 就地编辑(EditInPlace)是一种用户界面设计模式,允…...
【SCT63142FIB】15W高集成、高效率的无线功率发射PMIC
SCT63142FIB 15W高集成、高效率的无线功率发射PMIC 描述 SCT63142是一款高度集成的电源管理IC,可实现符合WPC规范的高性能,高效率和成本效益的无线电源发送系统,支持高达15W的功率传输,可与无线应用特定控制器或基于通用MCU的发送…...
STM32F4系列单片机新玩法---Micropython--pyBoard
只需要更改main.py文件的内容即可,例程CRTLV 1、流水灯: # main.py -- put your code here! import pyb while(1): for n in range(1,5) ledpyb.LED(n) led.on() pyb.delay(1000) led.off() 2、灯条,可以改变delay值来凸显效果…...
MFC中如何在工具条动态增加菜单
在C:\temp\VCSamples-master\VC2010Samples\MFC\Visual C 2008 Feature Pack\WordPad 这个例子中倒是有在工具条上动态增加菜单的方法,但有个缺陷,必须预先将需要的按钮定死。现将方法总结如下: 效果如下:点击前: 点击…...
行为型模式-迭代器模式
迭代器模式(Iterator Pattern)是一种行为型设计模式,主要用于提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露其内部表示。这个模式常被用于集合类对象(如列表、数组、图等)的实现中。 模式结构…...
SAP SD学习笔记17 - 投诉处理3 - Credit/Debit Memo依赖,Credit/Debit Memo
上一章讲了 请求书(发票)的取消。 SAP SD学习笔记16 - 请求书的取消 - VF11-CSDN博客 再往上几章,讲了下图里面的返品传票: SAP SD学习笔记14 - 投诉处理1 - 返品处理(退货处理)的流程以及系统实操&#…...
digit_eye开发记录(3): C语言读取MNIST数据集
在前两篇,我们解读了 MNIST 数据集的 IDX 文件格式,并分别用 C 和 Python 做了 读取 MNIST 数据集的实现。 基于 C 的代码稍长,基于 Python 的代码则明显更短,然而它们的共同特点是:依赖了外部库: 基于 C …...
快速学习GO语言总结
干货分享,感谢您的阅读!备注:本博客将自己初步学习GO的总结进行分享,希望大家通过本博客可以在短时间内快速掌握GO的基本程序编码能力,如有错误请留言指正,谢谢! 一、初步了解Go语言 …...
开源多媒体处理工具ffmpeg是什么?如何安装?使用ffmpeg将M3U8格式转换为MP4
目录 一、FFmpeg是什么二、安装FFmpeg(windows)三、将M3U8格式转换为MP4格式 一、FFmpeg是什么 FFmpeg是一款非常强大的开源多媒体处理工具,它几乎可以处理所有类型的视频、音频、字幕以及相关的元数据。 FFmpeg的主要用途包括但不限于&…...
Python面试实战:高效处理海量日志,找出高频IP
Python面试实战:高效处理海量日志,找出高频IP 问题描述 在处理海量服务器日志时,一个常见的需求是统计出现次数最多的IP地址。这不仅有助于分析网站访问流量,还能为安全监控提供有价值的信息。然而,当日志文件过大,无法一次性加载到内存时,如何高效地解决这个问题就成…...
基于Java Springboot蛋糕订购小程序
一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 微信…...
Java进程为什么会消失
深夜,办公室里只剩下电脑屏幕的微光。小王正在紧张地盯着生产环境的监控面板,突然发现一个重要的Java应用进程不见了,就像人间蒸发一般。这种情况在Java开发中并不罕见,让我们一起走进这个技术探案,揭开Java进程神秘消…...
继上一篇,设置弹框次数以及自适应图片弹框,部分机型(vivo)老手机不显示的问题
上一篇写的本来测试好多型号都无事, 今天下午公司的战斗机vivo横空冒出… 晕 弹框直接显示都出不来了,现在还有用这种老的机型的,但是没办法咯~ 前端遇到这种兼容性的问题就要勇于解决 主要解决了这几点: // 添加图片加载事件 <imgv-if"imageUrl":src"image…...
【docker】容器卷综合讲解,以及go实现的企业案例
容器卷(Volumes)基础讲解: 容器概念 容器卷(Volumes)是 Docker 提供的一种持久化存储机制,允许容器持久化数据,即使容器被删除或重新创建,数据仍然可以保留。 卷是独立于容器的&…...
八、利用CSS制作导航栏菜单
8.1 水平顶部导航栏 水平菜单导航栏是网站设计中应用范围最广的导航设计,一般放置在页面的顶部。水平导航适用性强,几乎所有类型的网站都可以使用。 如果导航过于普通,无法容纳复杂的信息结构,就需要在内容模块较多的情况…...
windows下安装node.js和pnpm
首先,一定要powershell右键选择管理员身份运行,否则第三个命令报错。 # 安装 fnm (快速 Node 管理器) winget install Schniz.fnm# 配置 fnm 环境 fnm env --use-on-cd | Out-String | Invoke-Expression# 下载并安装 Node.js fnm use --install-if-mis…...
MyBlog(五) -- 用户注册页面完善
文章目录 前言一、用户数据提交1. 表单2. 校验数据3. 模版文件1. wait_start2. user_register 二、验证1. 发送邮件1. 注册163邮箱后登录并设置2. 开启IMAP/SMTP服务, IMAP/SMTP服务3. 新增授权密码 2.邮箱内容3.点击注册4.发送邮件5. 激活账号1. 完善激活功能 -- user_active2…...
NLP任务四大范式的进阶历程:从传统TF-IDF到Prompt-Tuning(提示词微调)
引言:从TF-IDF到Prompt-Tuning(提示词微调),NLP的四次变革 自然语言处理(NLP)技术从最早的手工特征设计到如今的Prompt-Tuning,经历了四个重要阶段。随着技术的不断发展,我们的目标…...
量化交易系统开发-实时行情自动化交易-8.3.开拓者TBQuant平台
19年创业做过一年的量化交易但没有成功,作为交易系统的开发人员积累了一些经验,最近想重新研究交易系统,一边整理一边写出来一些思考供大家参考,也希望跟做量化的朋友有更多的交流和合作。 接下来会对于开拓者TBQuant平台介绍。 …...
第八课 Unity编辑器创建的资源优化_特效篇(Particle System)详解
无论是CPU还是GPU,粒子系统对其的影响面都是不容小觑的。随着项目的重度化和3A化,玩家的口味变挑剔了、游戏玩法复杂度变高了、画面的特效表现变复杂了......所以我们还是更加谨慎地对待粒子系统。 特效(Particle System) 游戏效…...
redis常见数据类型
Redis是一个开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理,支持多种数据类型。 一、数据类型介绍 String(字符串) Redis中最基本的数据类型。可以存储任何类型的数据,包括字符串、数字和二进制…...
【解决安全扫描漏洞】---- 检测到目标站点存在 JavaScript 框架库漏洞
1. 漏洞结果 JavaScript 框架或库是一组能轻松生成跨浏览器兼容的 JavaScript 代码的工具和函数。如果网站使用了存在漏洞的 JavaScript 框架或库,攻击者就可以利用此漏洞来劫持用户浏览器,进行挂马、XSS、Cookie劫持等攻击。 1.1 漏洞扫描截图 1.2 具体…...