关于ORM框架(Object Relational Mapping 对象关系映射),mybatis 与 JPA各有各的好,都实现了对DAO层(Data Access Object 数据访问对象)功能的强大封装。mybatis可以灵活地手写各种复杂的SQL, 性能也更好;JPA对于扩展实体对象属性字段更友好。
个人建议:追求短平快的小公司可以采用JPA,开发更高效;业务繁杂的大中型企业宜采用mybatis,追求性能与稳定。
JPA(Java Persistence API),(插句题外话:看到一些缩写词,我习惯找出他的全称,有助于理解相关技术的灵魂。)顾名思义就是Java 持久层API,确切地说JPA不是一个技术框架,而是一种接口规范标准,大多习惯用Hibernate框架来实现。
有关springboot的快速搭建配置,请回看历史分享springboot+mybatis。
springboot集成各种功能组件大多采用三步走,一般步骤不外乎:
- 依赖 - maven pom文件追加相关依赖
- 配置 - yml 文件追加相关配置 或 自定义配置
- 封装 - 根据架构设计组装自己的通用组件(工具)
接下来按部就班单刀直入JPA(假定springboot脚手架已搭建好且运行正常)。
1. maven添加JPA依赖
数据源依赖,同mybatis
<!--jpa-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.2.Final</version>
</dependency>
2. JPA 配置
1.yml(数据源配置,同mybatis)
spring:
#jpa
jpa:
show-sql: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
properties:
hibernate:
format_sql: false
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect # 默认使用innodb存储引擎
2.application(实体类及Repository扫描)
@EntityScan(basePackages = {"com.example.demo.domain"})
@EnableJpaRepositories(basePackages = {"com.example.demo.repository"})
@SpringBootApplication
public class DemoApplication{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. JPA 功能封装
1) entity
Domain
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Slf4j
@Data
@MappedSuperclass // 该注解作用:该父类不对应映射数据库中的表,但属性可以被子类继承
public class Domain implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'")
protected Date createTime;
@Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间'")
protected Date updateTime;
@JSONField(serialize = false)
@Column(columnDefinition = "TINYINT(2) NOT NULL DEFAULT 0 COMMENT '删除标志'")
protected Byte delFlag;
}
User
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@EqualsAndHashCode(callSuper = true)
@Data
@Entity
@Table(name = "user_user")
@DynamicInsert
@DynamicUpdate
public class User extends Domain {
@Column(columnDefinition = "VARCHAR(28) NOT NULL DEFAULT '' COMMENT '注册手机号或微信openId'")
private String account;
@Column(columnDefinition = "VARCHAR(32) NOT NULL DEFAULT '' COMMENT '注册登录密码'")
private String password;
}
2) repository
import com.example.demo.domain.Domain;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
public interface BaseRepository<S extends Domain> extends JpaSpecificationExecutor<S>, JpaRepository<S, Integer> {
default Specification<S> getSpecification(S example) {
return (root, criteriaQuery, criteriaBuilder) -> {
Path<Integer> id = root.get("id");
Path<Byte> delFlag = root.get("delFlag");
List<Predicate> predicateList = new ArrayList<>();
if (example.getId() != null && example.getId() > 0) {
predicateList.add(criteriaBuilder.equal(id, example.getId()));
}
if (example.getDelFlag() != null) {
predicateList.add(criteriaBuilder.equal(delFlag, example.getDelFlag()));
}
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
};
}
}
import com.example.demo.domain.User;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.util.StringUtils;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.List;
public interface UserRepository extends BaseRepository<User> {
default Specification<User> getSpecification(User example) {
return (root, criteriaQuery, criteriaBuilder) -> {
Path<Integer> id = root.get("id");
Path<String> account = root.get("account");
Path<Byte> delFlag = root.get("delFlag");
List<Predicate> predicateList = new ArrayList<>();
if (example.getId() != null && example.getId() > 0) {
predicateList.add(criteriaBuilder.equal(id, example.getId()));
}
if (example.getDelFlag() != null) {
predicateList.add(criteriaBuilder.equal(delFlag, example.getDelFlag()));
}
if (!StringUtils.isEmpty(example.getAccount())) {
predicateList.add(criteriaBuilder.equal(account, example.getAccount()));
}
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
};
}
User findByAccount(String account);
// native SQL
@Query(value = "select u.* from user_user u where u.id = ?1 or u.account = ?2 ", nativeQuery = true)
User queryUser(Integer id, String account);
// JPQL
@Modifying // 更新或删除时需要加此注解,查询时不需要
@Query("update User set account = :account where id = :id ")
User updateUserAccount(@Param("id") Integer id, @Param("account") String account);
}
3) service
BaseService:
import com.example.demo.po.PagerRequest;
import org.springframework.data.domain.Page;
import java.util.List;
public interface BaseService<T> {
/**
* saveOrUpdate
* @param domain
* @return
*/
T merge(T domain);
/**
* saveOrUpdate All
* @param domains
* @return
*/
List<T> mergeAll(Iterable<T> domains);
/**
* 删除
* @param domain
*/
void delete(T domain);
/**
* 批量删除
* @param domains
*/
void deleteAll(Iterable<T> domains);
/**
* 按主键查询
* @param id
* @return
*/
T findById(Integer id);
/**
* 按主键批量查询
* @param ids
* @return
*/
List<T> findAllById(Iterable<Integer> ids);
/**
* 按条件查询数据列表
* @param domain
* @return
*/
List<T> findByExample(T domain);
/**
* 按条件统计数量
* @param domain
* @return
*/
long count(T domain);
/**
* 分页查询数据列表
* @param pagerRequest
* @return
*/
Page<T> findByPage(PagerRequest pagerRequest);
}
import com.example.demo.domain.Domain;
import com.example.demo.po.PagerRequest;
import com.example.demo.repository.BaseRepository;
import com.example.demo.service.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.List;
@Slf4j
public abstract class BaseServiceImpl<T extends Domain> implements BaseService<T> {
@Autowired
private BaseRepository<T> baseRepository;
private Class<T> domain;
public BaseServiceImpl() {
ParameterizedType parameterizedType = ((ParameterizedType) getClass().getGenericSuperclass());
domain = (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
@Transactional(rollbackFor = Exception.class)
@Override
public T merge(T domain) {
return baseRepository.save(domain);
}
@Transactional(rollbackFor = Exception.class)
@Override
public List<T> mergeAll(Iterable<T> domains) {
return baseRepository.saveAll(domains);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void delete(T domain) {
baseRepository.delete(domain);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteAll(Iterable<T> domains) {
baseRepository.deleteAll(domains);
}
@Override
public T findById(Integer id) {
if(id != null && id > 0){
return baseRepository.findById(id).orElse(null);
}
return null;
}
@Override
public List<T> findAllById(Iterable<Integer> ids) {
if(ids != null){
return baseRepository.findAllById(ids);
}
return Collections.emptyList();
}
@Override
public List<T> findByExample(T domain) {
return baseRepository.findAll(baseRepository.getSpecification(domain));
}
@Override
public long count(T domain) {
return baseRepository.count(baseRepository.getSpecification(domain));
}
@Override
public Page<T> findByPage(PagerRequest pagerRequest) {
try {
T t = domain.newInstance();
// 查询参数封装转换 (如分页查询User, 创建UserRequest extends PagerRequest,
BeanUtils.copyProperties(pagerRequest, t);
// 排序处理
Sort sort = pagerRequest.getSort();
if (sort == null) {
sort = new Sort(Sort.Direction.DESC, "id");
}
// JPA分页从0开始, mybatis pagerHelper从1开始
PageRequest pageable = PageRequest.of((pagerRequest.getPageNum() - 1), pagerRequest.getPageSize(), sort);
// 在对应的repository中覆写getSpecification方法
return baseRepository.findAll(baseRepository.getSpecification(t), pageable);
} catch (Exception e) {
log.error("异常信息:{}", e.getMessage());
throw new RuntimeException("查询参数异常");
}
}
}
UserService:
import com.example.demo.domain.User;
public interface UserService extends BaseService<User> {
/**
* findBy + 实体对象属性,JPA会自动映射转换成查询SQL
* @param account
* @return
*/
User findByAccount(String account);
User queryUser(Integer id, String account);
User updateUserAccount(Integer id, String account);
}
import com.example.demo.domain.User;
import com.example.demo.repository.UserRepository;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends BaseServiceImpl<User> implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public User findByAccount(String account) {
return userRepository.findByAccount(account);
}
@Override
public User queryUser(Integer id, String account) {
return userRepository.queryUser(id, account);
}
@Transactional(rollbackFor = Exception.class)
@Override
public User updateUserAccount(Integer id, String account) {
return userRepository.updateUserAccount(id, account);
}
}
写在最后:ORM一般作为关系型数据库的持久层处理解决方案,对于非关系型数据库(也就是我们常说的NoSQL), 比如:redis, mongoDB, ES, solr等(虽然有些侧重搜索,但也都具有数据存储功能),spring也可以无缝集成,下次聊聊springboot + springdata,其实JPA也是基于springdata。