个人觉得JPA没有MybatisPlus好用,但公司用到,写下技术总结。
一. 介绍
ORM(Object-Relational Mapping) 表示对象关系映射。常见的 orm 框架:Mybatis(ibatis)、Hibernate、Jpa。JPA 规范本质上就是一种 ORM 规范,注意不是 ORM 框架——因为 JPA 并未提供 ORM 实现,它只是制 订了一些规范,提供了一些编程的 API 接口,底层需要 hibernate 作 为其实现类完成数据持久化工作。JPA是一个尽量避免写sql的框架,面向对象化的查询而非面向数据库。
优势:
单表查询,简单条件查询非常快
劣势:
多表关联存在子查询时不方便,分页存在问题,且容易循环注入导致栈溢出。
二. JPA的基本使用
1.导入包
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
2.配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/mytest
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver //驱动
jpa:
hibernate:
ddl-auto: update //自动更新
show-sql: true //日志中显示sql语句
3.创建实体
公司表
@Entity//声明实体类
@Table(name = "company")//建立实体类和表的映射关系
@Data//lombok自动生成get,set方法
public class CompanyEntity implements Serializable {
private static final long serialVersionUID = 1L;
//标识主键
@Id
//配置主键的生成策略
@GeneratedValue(strategy = GenerationType.IDENTITY)
//指定和表中id 字段的映射关系
@Column(name = "id")
private Integer id;
@Column(name = "name")
private String name;
//标识这个类与SysAreaEntity 表是一对一关系,即一个企业对应有一个区域
@OneToOne
//指定area_code关联SysAreaEntity 的 tree_code字段,referencedColumnName:指定引用主表的
//主键字段名称,不写默认关联SysAreaEntity 的主键id
@JoinColumn(name = "area_code", referencedColumnName = "tree_code")
private SysAreaEntity sysAreaEntity;
@Column(name = "address")
private String address;
@Column(name = "create_user_id")
private int createUserId;
//@OneToMany标识公司与设备是一对多关系,即一个公司下面有多个设备,且在DeviceEntity中用
//@ManyToOne
//@JoinColumn(name = "company_id")
//private CompanyEntity company1;
//对应设备与公司是多对一关系。mappedBy:指定从表实体类中引用主表对象的名称,
//cascade:指定要使用的级联操作,fetch:指定是否采用延迟加载
@OneToMany(mappedBy = "company1", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DeviceEntity> deviceEntities ;
}
区域表
@Entity
@Table(name="sys_area")
@Data
public class SysAreaEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
//这里与公司关联
@Column(name = "tree_code")
private String treeCode;
@Column(name = "name")
private String name;
}
设备表
@Entity
@Table(name = "company_device")
@Data
public class DeviceEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id;
//这里与公司关联,@ManyToOne标识一个设备对应多个公司
@ManyToOne
@JoinColumn(name = "company_id")
private CompanyEntity company1;
@Column(name = "total_number")
private Integer totalNumber;
}
4.搭建Dao层
//构建dao层接口继承JpaRepository包含了一般的curd方法,JpaSpecificationExecutor
//包含了条件查询查询的方法和分页查询方法
public interface CompanyDao extends JpaRepository<CompanyEntity, Integer>
, JpaSpecificationExecutor<CompanyEntity> {
//操作数据有三种方式
//第一种用它提供的方法
//第二种自己写sql
//@Query查询语句,nativeQuery = true是本机查询,接收参数可以用实体也可以用万能的Map
@Query(nativeQuery = true, value = "select id,name from company where if(?1 is null ,1=1,id in ?2)")
List<Map<String, Object>> findIdAndNameByIds(Integer isAll,List<Integer> companyIds);
//如果是修改或删除需要加上@Modifying,?2代表第二个参数,?1代表第一个参数
@Query(value="update company set name=?2 where id=?1",nativeQuery=true)
@Modifying
void updateCompany (Integer id, String name);
// 这种方式也可以实现复杂动态的查询,例如
@Query(value = "select * from t1 left join t2 on t1.id=t2.company_id where
if(:isAll is null ,1=1,t1.id in :companyIds) and
(:labelName is null or labels like %:labelName%)
limit :pageIndex,:pageSize",nativeQuery = true)
List<Map<String,Object>> getCompanies(@Param("labelName") String labelName,
@Param("pageIndex") Integer pageIndex, @Param("pageSize") Integer pageSize,
@Param("isAll")Integer isAll,@Param("companyIds") List<Integer> companyIds);
//第三种使用方法命名规则查询,可以根据方法命名来自动查询,但By后面的字段一定要是实体类中存在的
//且方法命名要符合规范。例如根据公司地址查询所有的公司:
List<CompanyEntity> findAllByAddress(String address);
}
5.搭建Service层
@Service
public class CompanyService {
private Logger log = LoggerFactory.getLogger(CompanyService.class);
@Autowired
CompanyDao companyDao;
//根据公司id和区域id分页查询公司列表
public Object pageByCondition(Integer pageIndex, Integer pageSize,String companyId,String areaId) {
//使用JPA自带的分页查询
Page<CompanyEntity> page = companyDao.findAll((Specification<CompanyEntity>) (root, query, criteriaBuilder) ->
{
List<Predicate> list = new ArrayList<>();
if (nul != companyId) {
list.add(criteriaBuilder.equal(root.get("id"), companyId));
}
// JoinType.INNER代表公司表与区域表采用内连接方式关联
Join<CompanyEntity, SysAreaEntity> areaEntityJoin = root.join("sysAreaEntity", JoinType.INNER);
if (null != areaId) {
list.add(criteriaBuilder.equal(areaEntityJoin.get("id"), areaId));
}
return criteriaBuilder.and(list.toArray(new Predicate[]{}));
}, PageRequest.of(pageIndex, pageSize, Sort.by(Sort.Direction.DESC, "id")));
// 因为ComanyEntity中包含了 List<DeviceEntity>,而每个 DeviceEntity 中含有一个CompanyEntity ,这里面的
//CompanyEntity 又包含了List<DeviceEntity>......,通过调试发现循环注入,如果直接返回页面会出现套娃现象的
//数据,所以要专门写一个方法来转换到页面数据CompanyVo
List<CompanyVo> result = entityToVo(page.getContent().toArray(new CompanyEntity[]{}))
return result ;
}
//实体转VO
public List<CompanyVo> entityToVo(CompanyEntity... entities) {
List<CompanyVo> vos = new ArrayList<>();
for (CompanyEntity entity : entities) {
CompanyVo vo = new CompanyVo();
vo.setName(entity.getName());
//设置区域名称
vo.setArea(entity.getSysAreaEntity().getName());
......
//把公司的设备单独拿出来处理
List<DeviceEntity> deviceEntities = entity.getDeviceEntities();
StringBuffer deviceNumber = new StringBuffer();
for (DeviceEntity deviceEntity : deviceEntities) {
deviceNumber.append(deviceEntity.getTotalNumber() + "、");
}
vo.setDeviceNumber(deviceNumber);
vos.add(vo);
}
return vos;
}
//新增,修改
@Transactional
public Object save(String companyName, Integer areaId, String address, String name) {
//新增企业,save方法如果有主键id则是修改,没有则是新增,且新增成功后会把id返回来,
CompanyEntity company = new CompanyEntity();
company.setName(companyName);
//实体类包含其它对象的,要先创造其它对象,这些对象要有主键id,否则会失败
Optional<SysAreaEntity> sysAreaEntity = sysAreaDao.findById(areaId);
company.setSysAreaEntity(sysAreaEntity.get());
company.setAddress(address);
companyDao.save(company);
return "ok";
}
//删除
@Transactional
public Object deleteById(Integer id){
companyDao.deleteById(id);
return "ok";
}
}
三. 补充使用方法命名规则查询的规范(部分常用)
Keyword | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | 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 |
After | findByStartDateAfter … | where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |