个人觉得JPA没有MybatisPlus好用,但公司用到,写下技术总结。
一. 介绍ORM(Object-Relational Mapping) 表示对象关系映射。常见的 orm 框架:Mybatis(ibatis)、Hibernate、Jpa。JPA 规范本质上就是一种 ORM 规范,注意不是 ORM 框架——因为 JPA 并未提供 ORM 实现,它只是制 订了一些规范,提供了一些编程的 API 接口,底层需要 hibernate 作 为其实现类完成数据持久化工作。JPA是一个尽量避免写sql的框架,面向对象化的查询而非面向数据库。
优势:
单表查询,简单条件查询非常快
劣势:
多表关联存在子查询时不方便,分页存在问题,且容易循环注入导致栈溢出。
2.配置文件org.springframework.data spring-data-jpa
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 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 JpaRepository5.搭建Service层, JpaSpecificationExecutor { //操作数据有三种方式 //第一种用它提供的方法 //第二种自己写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
@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 page = companyDao.findAll((Specification) (root, query, criteriaBuilder) ->
{
List list = new ArrayList<>();
if (nul != companyId) {
list.add(criteriaBuilder.equal(root.get("id"), companyId));
}
// JoinType.INNER代表公司表与区域表采用内连接方式关联
Join 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 中含有一个CompanyEntity ,这里面的
//CompanyEntity 又包含了List......,通过调试发现循环注入,如果直接返回页面会出现套娃现象的
//数据,所以要专门写一个方法来转换到页面数据CompanyVo
List result = entityToVo(page.getContent().toArray(new CompanyEntity[]{}))
return result ;
}
//实体转VO
public List entityToVo(CompanyEntity... entities) {
List vos = new ArrayList<>();
for (CompanyEntity entity : entities) {
CompanyVo vo = new CompanyVo();
vo.setName(entity.getName());
//设置区域名称
vo.setArea(entity.getSysAreaEntity().getName());
......
//把公司的设备单独拿出来处理
List 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 = 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 |



