目前市场上Java语言ORM框架有Mybatis、Hibernate、Spring Data JPA,其中JPA底层还是使用Hibernate实现,引用JPQL查询语言,属于Spring整个生态体系的一部分,使用起来比较方便,加快了研发效率。
Part-1: Jpa基础知识
本章节将通过一些例子让大家对Jpa的日常使用有一个了解。
数据库准备
CREATE TABLE `user_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`first_name` varchar(200) DEFAULT NULL,
`last_name` varchar(200) DEFAULT NULL,
`create_time` datetime NOT NULL,
`create_user_id` bigint NOT NULL,
`last_upgrade_time` datetime DEFAULT NULL,
`last_upgrade_user_id` bigint DEFAULT NULL,
`delete_time` datetime DEFAULT NULL,
`delete_user_id` bigint DEFAULT NULL,
`is_deleted` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=115 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
配置JPA
pom文件引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://host:port/DBSchema
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.show-sql=true #是否显示数据库语句
常用Repository类结构
主要实现类SimpleJpaRepository和QuerydslJpaRepository,其中QuerydslJpaRepository为框架不赞成使用类。
注解型查询方法
分页查询
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
@Query("select u from UserInfo u where u.firstName like concat('%',:firstname,'%') ")
List
<UserInfo> findByFirstName(@Param("firstname") String firstName
,Pageable pageable
);
}
List
<UserInfo> userInfos
= userInfoRepository
.findByFirstName("ray",PageRequest
.of(0,10));
排序查询
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
@Query("select u from UserInfo u where u.firstName like concat('%',:firstname,'%') ")
List
<UserInfo> findByFirstName(@Param("firstname") String firstName
, Sort sort
);
}
Sort sort
=Sort
.by("lastName").descending()
List
<UserInfo> softUserInfos
=userInfoRepository
.findByFirstName("ray",sort
);
实体声明和通用注解
基础注解
@Entity:代表对象将被JPA管理的实体@Table:指定数据库表@Id定义为数据库的主键,一个实体必须有一个@GeneratedValue为主键生成策略:
TABLE:通过表产生主键,框架有表模拟序列产生主键,使用该策略可以使应用更易于数据库移植SEQUENCE:通过序列产生主键,通过@SequenceGenerator指定序列名,MySql不支持这个方法IDENTITY:采用数据库ID增长,一般用于MySql数据库AUTO:JPA自动选择合适的策略,默认选项 @Basic:表示该属性是数据库表的字段映射@Transient:表示属性并非映射到数据库@Column:定义属性映射到数据库列名@Temporal:用来设置Date类型映射到对应精度的字段
TemporalType.DATE:只有日期TemporalType.TIME:只有时间TemporalType.TIMESTAMP:日期+时间 @Enumerated:枚举字段
EnumType.ORDINAL:数组下标,一般枚举后期都会扩展,可能会引起数组下标变化故不推荐使用EnumType.STRING:枚举Name 关联查询
@JoinColumn:定义多个字段关联关系@OneToOne:可以双向关联,也可以仅配置一方@OneToMany与@ManyToOne@OrderBy:关联查询时排序@JoinTable:关联关系表@ManyToMany:代表多对多@EntityGraph:提升关联查询效率,上边的如果有多条数据会出现多条语句查询,可以通过join查询方式提升效率 实体继承
Part-2:逻辑删除实现方法
单实体全局处理
@Entity
@Table(name
= "user_info")
@Where(clause
= "is_deleted=0")
@SQLDelete(sql
= "update user_info set is_deleted=1 where id=?")
public class UserInfo extends BaseEntity {
@Column(name
= "first_name", nullable
= true)
private String firstName
;
@Column(name
= "last_name", nullable
= true)
private String lastName
;
....省略属性Get
/Set
....
}
该方法通过@Where注解将所有查询实体都会加上is_deleted=0的查询条件,但是真删除功能会被覆盖
继承PagingAndSortingRepository实现相关查询接口,增加假删除接口
@NoRepositoryBean
public interface MyBaseRepository<T
extends BaseEntity, ID
extends Serializable> extends PagingAndSortingRepository<T, ID> {
@Override
@Transactional(readOnly
= true)
@Query("select e from #{#entityName} e where e.deleted=false")
List
<T> findAll();
@Override
@Transactional(readOnly
= false)
@Query("select e from #{#entityName} e where e.id in ?1 and e.deleted=false")
List
<T> findAllById(Iterable
<ID> iterable
);
@Override
@Transactional(readOnly
= true)
@Query("select e from #{#entityName} e where e.deleted=false and e.id=?1")
Optional
<T> findById(ID id
);
@Override
@Transactional(readOnly
= true)
default boolean existsById(ID id
) {
return findById(id
) != null
;
}
@Override
@Transactional(readOnly
= true)
@Query("select count(e) from #{#entityName} e where e.deleted=false ")
long count();
@Modifying
@Query("update #{#entityName} e set e.deleted=true")
void logicDeleteAll();
@Transactional
@Modifying
@Query("update #{#entityName} e set e.deleted=true where e.id=?1")
void logicDelete(ID id
);
@Modifying
@Transactional
default void logicDelete(T entity
) {
logicDelete((ID
) entity
.getId());
}
@Transactional
default void logicDelete(Iterable
<? extends T> entities
) {
entities
.forEach(entity
-> {
logicDelete((ID
) entity
.getId());
});
}
}
该方法需要自己在自定义查询方法的时候手动加上is_deleted条件进行过滤
Part-3:多数据源配置
配置文件
# 数据库1
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db1.url=jdbc:mysql://localhost:3306/db1
spring.datasource.db1.username=root
spring.datasource.db1.password=12345678
# 数据库2
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db2.url=jdbc:mysql://localhost:3306/db2
spring.datasource.db2.username=root
spring.datasource.db2.password=12345678
配置声明
@Configuration
public class PropertieConfig {
@Autowired
private JpaProperties jpaProperties
;
@Autowired
private HibernateProperties hibernateProperties
;
@Bean(name
= "vendorProperties")
public Map
<String, Object> getVendorProperties() {
return hibernateProperties
.determineHibernateProperties(jpaProperties
.getProperties(),
new HibernateSettings());
}
}
数据源1配置
@Configuration
public class DB1DataSourceConfig {
@Bean(name
= "db1DataSource")
@ConfigurationProperties(prefix
= "spring.datasource.db1")
public DataSource
db1DataSource() {
return new DruidDataSource();
}
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef
="db1EntityManagerFactory",
transactionManagerRef
="db1TransactionManager",
basePackages
= { "lsf.db1.repository" })
public class DB1EntryManagerConfig {
@Autowired
@Qualifier("db1DataSource")
private DataSource db1DataSource
;
@Autowired
@Qualifier("vendorProperties")
private Map
<String, Object> vendorProperties
;
@Bean(name
= "db1EntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean
entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder
) {
return builder
.dataSource(db1DataSource
)
.properties(vendorProperties
)
.packages("lsf.db1.entity")
.persistenceUnit("primaryPersistenceUnit")
.build();
}
@Bean(name
= "db1EntityManager")
public EntityManager
entityManager(EntityManagerFactoryBuilder builder
) {
return entityManagerFactoryPrimary(builder
).getObject().createEntityManager();
}
@Bean(name
= "db1TransactionManager")
public PlatformTransactionManager
transactionManagerPrimary(EntityManagerFactoryBuilder builder
) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder
).getObject());
}
}
数据源2配置
@Configuration
public class DB2DataSourceConfig {
@Bean(name
= "db2DataSource")
@ConfigurationProperties(prefix
= "spring.datasource.db2")
public DataSource
db2DataSource() {
return new DruidDataSource();
}
}
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef
="db2EntityManagerFactory",
transactionManagerRef
="db2TransactionManager",
basePackages
= { "lsf.db2.repository" })
public class DB2EntryManagerConfig {
@Autowired
@Qualifier("db2DataSource")
private DataSource db2DataSource
;
@Autowired
@Qualifier("vendorProperties")
private Map
<String, Object> vendorProperties
;
@Bean(name
= "db2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactoryPrimary(EntityManagerFactoryBuilder builder
) {
return builder
.dataSource(db2DataSource
)
.properties(vendorProperties
)
.packages("lsf.db2.entity")
.persistenceUnit("primaryPersistenceUnit")
.build();
}
@Bean(name
= "db2EntityManager")
public EntityManager
entityManager(EntityManagerFactoryBuilder builder
) {
return entityManagerFactoryPrimary(builder
).getObject().createEntityManager();
}
@Bean(name
= "db2TransactionManager")
public PlatformTransactionManager
transactionManagerPrimary(EntityManagerFactoryBuilder builder
) {
return new JpaTransactionManager(entityManagerFactoryPrimary(builder
).getObject());
}
}
Part-4:Druid集成
Druid引入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.24</version>
</dependency>
配置信息
#拦截器为可视化界面提供数据
spring.datasource.filesystem.filters=stat
#初始化连接池个数
spring.datasource.filesystem.initialSize=5
#最小连接池个数
spring.datasource.filesystem.minIdle=5
#最大连接池个数
spring.datasource.filesystem.maxActive=20
# 配置获取连接等待超时的时间,单位毫秒,缺省启用公平锁,并发效率会有所下降
spring.datasource.filesystem.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.filesystem.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.filesystem.minEvictableIdleTimeMillis=300000
#是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.filesystem.poolPreparedStatements=false
#要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
spring.datasource.filesystem.maxOpenPreparedStatements= -1
#申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.filesystem.testOnBorrow=true
#归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
spring.datasource.filesystem.testOnReturn=false
#建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.filesystem.testWhileIdle=false
#物理连接初始化的时候执行的sql
spring.datasource.filesystem.connectionInitSqls=
#根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
spring.datasource.filesystem.exceptionSorter
#用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
spring.datasource.filesystem.validationQuery
声明监控拦截器
@WebFilter(filterName
= "druidWebStatFilter",
urlPatterns
= "/*",
initParams
= {
@WebInitParam(name
= "exclusions", value
= "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"),
})
public class DruidStatFilter extends WebStatFilter {
}
druid监控视图配置
@WebServlet(urlPatterns
= "/druid/*", initParams
={
// @WebInitParam(name
="deny",value
="192.168.6.73"), // IP黑名单
(存在共同时,deny优先于allow
)
@WebInitParam(name
="loginUsername",value
="admin"), // 用户名
@WebInitParam(name
="loginPassword",value
="admin"), // 密码
@WebInitParam(name
="resetEnable",value
="true") // 禁用HTML页面上的“Reset All”功能
})
public class DruidStatViewServlet extends StatViewServlet {
private static final long serialVersionUID
= 7359758657306626394L
;
}
启动类添加组件扫描
@SpringBootApplication
@ServletComponentScan
public class StudyDruidApplication {
public static void main(String
[] args
) {
SpringApplication
.run(StudyDruidApplication
.class, args
);
}
}
Part-5:事件使用
添加实体类注解
@Entity
@Table(name = "user_info")
@EntityListeners({ActionsLogsAuditListener.class})
public class UserInfo extends BaseEntity
声明事件
public class ActionsLogsAuditListener {
private static final Logger logger
= LoggerFactory
.getLogger(ActionsLogsAuditListener
.class);
@PrePersist
private void prePersist(Object entity
) {
this.notice(entity
, OperateType
.create
,OperateStatus
.before
);
}
@PostPersist
private void postPersist(Object entity
) {
notice(entity
, OperateType
.create
,OperateStatus
.after
);
}
@PreRemove
private void preRemove(Object entity
) {
notice(entity
, OperateType
.remove
,OperateStatus
.before
);
}
@PostRemove
private void postRemove(Object entity
) {
notice(entity
, OperateType
.remove
,OperateStatus
.after
);
}
@PreUpdate
private void preUpdate(Object entity
) {
notice(entity
, OperateType
.update
,OperateStatus
.before
);
}
@PostUpdate
private void postUpdate(Object entity
) {
notice(entity
, OperateType
.update
,OperateStatus
.after
);
}
@PostLoad
private void postLoad(Object entity
) {
this.notice(entity
, OperateType
.load
,OperateStatus
.after
);
}
private void notice(Object entity
, OperateType operateType
, OperateStatus operateStatus
) {
if (operateStatus
== OperateStatus
.after
) {
logger
.info("{}执行了{}操作", entity
, operateType
.getDescription());
} else if (operateStatus
== OperateStatus
.before
) {
logger
.info("{}准备执行{}操作", entity
, operateType
);
}
}
enum OperateStatus
{
before("操作前"),
after("操作后");
private final String description
;
OperateStatus(String description
) {
this.description
= description
;
}
public String
getDescription() {
return description
;
}
}
enum OperateType
{
create("创建"),
remove("删除"),
update("修改"),
load("查询");
private final String description
;
OperateType(String description
) {
this.description
= description
;
}
public String
getDescription() {
return description
;
}
}
}
Part-6:事务Transactional
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public boolean saveUserInfo() throws Exception
事务传播等级
REQUIRED:当前存在事务则加入,没有事务则创建SUPPORTS:存在事务则加入,没有事务则以非事务方式运行MANDATORY:存在事务则加入,没有事务则抛异常REQUIRES_NEW:创建新事务,存在则挂起当前事务NOT_SUPPORTED:以非事务方式运行,存在事务则挂起当前事务NEVER:以非事务方式运行,存在事务则抛出异常NESTED:当前存在事务则创建一个事务作为当前事务嵌套事务来运行,如果没有事务取值等价于REQUIRED 事务隔离等级
DEAULT:底层数据库默认隔离级别,大部分情况是READ_COMMITEDREAD_UNCOMMITED:表示一个事务可以读取另一个事务修改但没有提交数据,不能防止脏读和不可重复读READ_COMMITED:表示一个事务只能读物另一个事务已经提交的数据,可以防止脏读REPEATABLE_READ:表示一个事务在整个过程中可以多次重复执行某个查询,不管是否中间有新增数据,查询结果都一样,可以防止脏读和重复读SERIALIABLE:所有事务依次执行,可以解决脏读、不可重复读、幻读,但是性能太差; 回滚机制:
如果不做任何处理,只要事务覆盖方法失败都会回滚声明RollbackFor当方法体抛出指定异常类明则执行回滚,其它不回滚声明noRollbackFor当方法体抛出指定异常类明则不执行回滚,其它回滚
Part-7:高阶应用:自定义Repository实现
根据EntitryManager进行仓储自定义封装来增强功能,如批量保存、逻辑删除等,代码样例如下:
@NoRepositoryBean
public class BaseDaoImpl implements BaseDao {
@PersistenceContext
private EntityManager entityManager
;
@Transactional
@Override
public <T
extends BaseEntity> boolean save(T entity
) {
this.getSession().save(entity
);
return true;
}
private Session
getSession() {
return entityManager
.unwrap(Session
.class);
}
}
参考资料
1、Jpa实现假删除:https://my.oschina.net/weechang93/blog/1576594 2、书籍购买点击:《Spring Data JPA从入门到精通》-张振华 3、Druid说明文档:https://github.com/alibaba/druid/wiki