JAVA学习笔记:JPA-JPQL&乐观锁

本文介绍了JAVA中的JPA和JPQL查询语言的使用,包括模型映射、基本查询、子查询、分页查询等。同时,文章详细探讨了乐观锁在解决并发事务问题中的应用,解释了事务并发可能引发的问题和解决方案,以及JPA的优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 配置查询对象的模型

1.1 E-R图

在这里插入图片描述

1.2 模型映射(代码省略get/set方法)

有的关系配置的单向,可根据情况自行配置为双向

1.2.1 部门

多个部门一个项目经理
一个部门多个员工

@Entity
@Table(name = "t_department")
public class Department {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String provice;
    private String city;
    private String street;
    private String sn;
    @ManyToOne(fetch = FetchType.LAZY)
    private Employee manager;
    @OneToMany(mappedBy = "department")
    private Set<Employee> employees = new HashSet<Employee>();
}

1.2.2 员工

多个员工一个部门

@Entity
@Table(name = "t_employee")
public class Employee {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private BigDecimal salary;
    private Date hireDate;
    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;
}

1.2.3 电话

多个电话属于一个员工

@Entity
@Table(name = "t_phone")
public class Phone {
    @Id
    @GeneratedValue
    private Long id;
    private String types;
    private String number;
    @ManyToOne
    private Employee employee;
}

1.2.4 项目

多个项目由一个项目经理负责
项目和员工为多对多关系

@Entity
@Table(name = "t_project")
public class Project {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    @ManyToOne
    private Employee manager;
    @ManyToMany(cascade = CascadeType.REMOVE)
    @JoinTable(name = "t_project_employee", joinColumns = { @JoinColumn(name = "project_id") }, inverseJoinColumns = {@JoinColumn(name = "employee_id") })
    private Set<Employee> employees = new HashSet<Employee>();
}

2. JPQL

2.1 JPQL书写规则

  1. JPQL是面向对象的,不能写表名,列名,只能写java的类名和属性名,区分大小写
  2. 关键字与sql一样,不区分大小写
  3. 不能写select * 要写select 别名,select u from User u;
  4. 查询是注意接收结果列表的类型,否则会出现类型转换错误,用类或者Objec[]
String jpql = "select o from Employee o";
EntityManager entityManager = JpaUtil.getEntityManager();
Query query = entityManager.createQuery(jpql);
List<Employee> resultList = query.getResultList();
for (Employee employee : resultList) {
	System.out.println(employee);
}

2.2 基本查询

  1. 查询所有员工:select o from Employee o
  2. 查询所有员工的姓名和所属部门名称:select o.id,o.name,o.department.name from Employee o
  3. 查询出所有在成都和广州工作的员工:select o,o.department.city from Employee o where o.department.city = ? or o.department.city = ?;
    query.setParameter(1, "成都").setParameter(2, "广州");
  4. 查询出所有员工信息,按照月薪排序:select o from Employee o order by salary desc
  5. 查询出所有员工信息,按照部门编号排序:select o from Employee o order by o.department.sn asc
  6. 查询出在恩宁路和八宝街上班的员工信息:select o from Employee o where o.department.street in (?,?)
    query.setParameter(1, "恩宁路").setParameter(2, "八宝街");
  7. 查询出工资在5000-6000的员工:select o from Employee o where o.salary between ? and ?
    query.setParameter(1, new BigDecimal("5000")).setParameter(2, new BigDecimal("6000"));
  8. 查询出姓名包含er或者en的员工:select o from Employee o where o.name like ? or o.name like ?
    query.setParameter(1, "%er%").setParameter(2, "%en%");

2.3 distinct去重

  1. 查询出有员工的部门:select distinct e.department from Employee e

2.4 集合的操作

  1. 查询出有员工的部门:select d from Department d where d.employees.size > 0
  2. 查询出部门信息,按照部门的员工人数排序:select d from Department d order by d.employees.size asc
  3. 查询出没有员工参与的项目:select p from Project p where p.employees.size=? query.setParameter(1, 0);

2.5 join

  1. 查询出所有员工及部门名称:select e,d.name from Employee e left join e.department d
  2. 查询出市场部员工信息及电话select e,p.number from Phone p join p.employee e where e.department.name like ?
    query.setParameter(1, "%市场%");

2.6 聚集(组)函数/GROUP/HAVING

  1. 查询出各个部门员工的平均工资和最高工资:select e.department.name,avg(e.salary),max(e.salary) from Employee e group by e.department.sn
  2. 查询出各个项目参与人数报表:select p.name,p.employees.size from Project p group by p.name
    不使用聚集:select p.name,p.employees.size from Project p

2.7 子查询

  1. 查询出大于平均工资的员工信息:select o from Employee o where o.salary > (select avg(e.salary) from Employee e)

2.8 分页查询

select o from Employee o
query.setFirstResult(0).setMaxResults(10);

2.9 获取记录总数

注意:返回类型是Long
select count(o) from Employee o
Long singleResult = (Long) query.getSingleResult();

3. 原生SQL查询

3.1 返回对象数组

EntityManager entityManager = JpaUtil.getEntityManager();
String sql = "select * from t_employee";
Query nativeQuery = entityManager.createNativeQuery(sql);
List<Object[]> resultList = nativeQuery.getResultList();
for (Object[] obj : resultList) {
	System.out.println(Arrays.toString(obj));
}

3.2 返回模型对象

EntityManager entityManager = JpaUtil.getEntityManager();
String sql = "select * from t_employee";
Query nativeQuery = entityManager.createNativeQuery(sql,Employee.class);
List<Employee> resultList = nativeQuery.getResultList();
for (Employee obj : resultList) {
    System.out.println(obj);
}

3.3 有查询条件

EntityManager entityManager = JpaUtil.getEntityManager();
String sql = "select * from t_employee where salary >= ?";
Query nativeQuery = entityManager.createNativeQuery(sql,Employee.class);
nativeQuery.setParameter(1, new BigDecimal("6000"));
List<Employee> resultList = nativeQuery.getResultList();
for (Employee obj : resultList) {
    System.out.println(obj);
}

4. 事务并发

事务的特性ACID:
原子性(atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行;
一致性(consistent),事务在完成时,必须使所有的数据都保持一致状态;
隔离性(insulation),由事务并发所作的修改必须与任何其它并发事务所作的修改隔离;
持久性(Duration),事务完成之后,它对于系统的影响是永久性的。
多个事务同时运行,这就是事务并发。

4.1 事务并发带来的问题

4.1.1 第一类丢失更新

两个人同时投票,获取的票数都为1000,甲投票成功,总票数为1001,乙投票失败,总票数又被修改为1000,导致最后一票都没有投成功。

4.1.2 脏读

两个人投票,总票数为1000,甲投票成功后,乙获取总票数就为1001,然后甲取消投票,乙提交投票,乙因为获取了脏数据,导致最后总票数为1002,乙投了两票。

4.1.3 虚读

虚读问题并不严重,比如获取网站的总注册人数,当获取了之后又有新用户注册,导致实际注册人数比获取的数多。

4.1.4 不可重复读

不可重复读问题也不太严重,在一个事务中前后两次读取的结果并不一致,导致了不可重复读。

4.1.5 第二类丢失更新

多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变。
两个人同时投票,获取的票数都为1000,甲投票成功,总票数为1001,乙投票也成功,总票数又被修改为1001,导致最后两个人投票成功只增加了一票。

4.2 数据库事务隔离级别

在这里插入图片描述

4.3 悲观锁(不使用)

如果使用了悲观锁(加了一个行锁),如果事务没有被释放,就会造成其他事务处于等待
使用数据库提供的锁机制实现悲观锁。
如果数据库不支持设置的锁机制,JPA会使用该数据库提供的合适的锁机制来完成,而不会报错。
使用entityManager.find(class,id,LockModeType);加悲观锁,相当于发送SELECT … FOR UPDATE(加了一个行锁)
使用entityManager.lock(object,LockModeType);加悲观锁,相当于发送SELECT id FROM … FOR UPDATE(加了一个行锁)

4.4 乐观锁

可以通过添加一个私有字段Integer version的方式,添加@Version注解,更新数据时,版本号加一,如果Version不能匹配,就会报错

public void testLock(){
        try {
            EntityManager entityManager1 = JpaUtil.getEntityManager();
            EntityManager entityManager2 = JpaUtil.getEntityManager();
            entityManager1.getTransaction().begin();
            entityManager2.getTransaction().begin();
            Product product1 = entityManager1.find(Product.class, 1L);
            Product product2 = entityManager2.find(Product.class, 1L);
            product1.setNum(product1.getNum()-1);
            product2.setNum(product2.getNum()-1);
            entityManager1.getTransaction().commit();
            entityManager2.getTransaction().commit();
            entityManager1.close();
            entityManager2.close();
        } catch (Exception e) {
            EntityManager entityManager3 = JpaUtil.getEntityManager();
            entityManager3.getTransaction().begin();
            Product product3 = entityManager3.find(Product.class, 1L);
            Long num = product3.getNum();
            if (num>0){
                product3.setNum(num-1);
            }else {
                System.out.println("对不起,库存为0");
            }
            entityManager3.getTransaction().commit();
            entityManager3.close();
        }
    }

4.5 JPA优化

  1. 使用双向一对多关联,不使用单向一对多
  2. 灵活使用单向多对一关联
  3. 不用一对一,用多对一取代,如果要用一对一,不要使用共享主键一对一,使用唯一外键一对一
  4. 配置对象二级缓存,查询缓存(jpql查询),没有查询条件才使用查询缓存
  5. 组合关系集合使用list(顺序,重复),多对多集合使用set
  6. 表字段要少,表关联不要怕多,有二级缓存,设计表尽量达到第三范式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值