JPA使用总结

个人觉得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";
	}
}

三. 补充使用方法命名规则查询的规范(部分常用)

KeywordSampleJPQL
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs, findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
AfterfindByStartDateAfter …where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值