orm思想
- 将实体类与数据表进行关联。
- 将实体类的字段与数据表的字段进行关联。
- 我们就可以像操作实体类一样操作数据库了,可以避免编写繁琐的sql语句。
- hibernate框架是一套全自动的orm框架
springdata jpa,jpa,hibernate之间的关系
- jpa规范本身提供了一套接口,是一套基于orm思想的规范。只要实现了jpa规范的框架,都可以使用jpa的接口进行操作。hibernate实现了jpa规范。
- 比如,我们使用springdata jpa操作数据库时,内部干活的依然是hibernate框架,真正底层走的还是jdbc。
- jpa的目标是提供更加简单的编程模型,方便开发人员的使用。
- jpa还定义了独特的面向对象的查询语言,JPQL。
- 而springdata jpa是对jpa规范的进一步封装。
springdata jpa的底层流程
- 首先我们会定义一个接口继承两个接口JpaRepository ,JpaSpecificationExecutor
- 当我们调用springdata jpa的接口进行数据表操作时,spring会通过动态代理技术生成一个动态代理对象,该对象也实现了那两个接口。
- 然后此动态代理对象会调用hibernate框架进行操作,hibernate底层再通过jdbc操作数据库。
springdata jpa中实体类的配置
- 实体类与数据库表映射配置。
@Entity //表明该类 (UserEntity) 为一个实体类,默认类名为表名
@Table(name = "user_message") //当表名与类名不同,使用@Table注解说明
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"}) //防止因为有nul字段产生异常
public class UserMessage{
}
- 类成员与数据库表主键映射的配置。
@Id //表明该字段为主键
@GeneratorValue //配置主键的生成策略,它有两个属性,分别是strategy和generator
//generator属性的值是一个字符串,默认为"",其声明了主键生成器的名称
//strategy属性:提供四种值:-AUTO主键由程序控制, 是默认选项 ,不设置就是这个
//-IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持
//-SEQUENCE 通过数据库的序列产生主键, MYSQL不支持
//-Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植
@Column(name = "userid") //表示成员与字段的映射
- 类成员与普通字段的映射
@Column(name = "name") //对于普通字段只需要设置此注解即可
springdata jpa的基本使用
- 在创建项目时选择web模块和jpa模块,jdbc模块,mysql模块
- 在全局配置文件配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.type=com.alibaba.druid.pool.
#create 启动时删数据库中的表,然后创建,退出时不删除数据表
#create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
#update 如果启动时表格式不一致则更新表,原有数据保留
#validate 项目启动表结构进行校验 如果不一致则报错
spring.jpa.hibernate.ddl-auto=update
- 首先编写一个实体类 Use 使用注解配置表与字段的映射r。 需要注意实体类要有无参构造。
public class User {
@Id
@GeneratedValue
private int id;
@Column(name = "name")
private String name;
@Column(name = "city")
private String city;
//geter setter 构造
}
- 然后编写一个dao层接口,继承JpaRepository<实体类,主键类型> 和JpaSpecificationExecutor<实体类>
//JpaRepository封装了基本的crud操作
//封装了复杂查询,比如分页
public interface UserResporisty extends JpaRepository<User,Integer> ,JpaSpecificationExecutor<User> {
}
- JpaRepository接口常用的自带的操作数据表的方法
@Autowired
UserRepository userRepository;
@Test
public void test() {
//添加或者更新一个记录 如果添加对象中没有主键表示保存,有主键表示更新
User save = userRepository.save(new User());
//根据主键查询一个记录
User one = userRepository.getOne("id");
//根据主键删除一个记录
userRepository.deleteById("id");
//查询所有记录 返回一个list集合
List<User> all = userRepository.findAll();
//查询表中记录的总数
userRepository.count();
//查询指定主键的记录是否存在
boolean existsById = userRepository.existsById("id");
}
- jpql进行查询(基于实体类和成员进行查询)
public interface UserResporisty extends JpaRepository<User,Integer> ,JpaSpecificationExecutor<User> {
//使用@Query注解来支持jpql查询,jpql语句写到注解中,?1表示第一个参数 同样?2表示第二个参数
@Query("SELECT u.login FROM User u WHERE u.id = ?1")
String findUserNameByID(String user_id);
//同样可以使用jpql完成更新操作
@Transactional //对于更新操作必须添加事务支持否则会报错,
//在单元测试方法中注明事务,事务会自动回滚,可以设置@Rollback(value = false)不自动回滚
@Modifying //代表此方法是一个更新操作
@Query("update User m set m.readStatus = true where m.id = ?1")
void updateReadStatusById(String id);
}
- 也可以在query注解中声明nativeQuery = true,支持使用原生sql语句进行查询
public interface UserResporisty extends JpaRepository<User,Integer> ,JpaSpecificationExecutor<User> {
//使用原生sql进行查询
@Query(value = "select * from t_user",nativeQuery = true)
List<User> findAll();
//原生sql也支持占位符
@Query(value = "select * from t_user where cust_name=? ",nativeQuery = true)
User findByName(String name);
}
- 两表关联查询 查询的每条记录以Object 数组接收。
public interface SlaItchatDataRepository extends JpaRepository<User,String> {
//全量查询情况1
@Query(value = "select * from table1 left join table2 on table1.id = table2.id", nativeQuery = true)
List<Object []> findjoindata();
}
编程式事务管理与声明式事务管理
- 编程式事务管理使用TransactionTemplate 它可以手动控制事务的开启 提交和回滚。
- 声明式事务管理基于springAOP实现,本质是将方法前后进行拦截,添加事务处理的功能。
- 声明式事务管理使用非侵入式开发方式,将事务功能与业务代码分离,缺点是最细粒度只能作用到方法级,无法作用到代码块级别。
JPA事务管理
-
在dao层代码@Modifying注解
①在@Query注解中,编写JPQL实现DELETE和UPDATE操作的时候,必须加上@modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作。
②UPDATE或者DELETE操作需要使用事务,此时需要定义Service层,在Service层的方法上添加事务操作。
@Modifying
@Query(value=“UPDATE hr_employee_contract t SET t.deleteStatus=1 WHERE t.id=?1”,nativeQuery = true)
void delete(Long id); -
在service层代码使用@Transactional手动开启事务管理
@Transactional注解其中使用较多的三个属性:readOnly、propagation、isolation。其中propagation属性用来枚举事务的传播行为,isolation用来设置事务隔离级别,readOnly进行读写事务控制。
@Transactional
@Override
public void delete(Long id) {
employeeContractDao.delete(id);
} -
readOnly属性
从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)
可以避免不可重复读的问题。
事务实例
- 编写实体类和dao层代码
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
}
- 在service层实现转账功能
@Service
public class UserService {
@Autowired
private UserDao userDao;
//转账 账户1向账户2转账 100元
public void trans1(){
User u1 = userDao.getOne(4);
User u2 = userDao.getOne(5);
u1.setMoney(u1.getMoney()-100);
u2.setMoney(u2.getMoney()+100);
userDao.save(u1);
userDao.save(u2);
}
//转账 账户1向账户2转账 100元
public void trans2(){
User u1 = userDao.getOne(4);
User u2 = userDao.getOne(5);
u1.setMoney(u1.getMoney()-100);
userDao.save(u1);
int a = 1 / 0; //制造一个异常
u2.setMoney(u2.getMoney()+100);
userDao.save(u2);
}
//转账 账户1向账户2转账 100元
@Transactional
public void trans3(){
User u1 = userDao.getOne(4);
User u2 = userDao.getOne(5);
u1.setMoney(u1.getMoney()-100);
userDao.save(u1);
int a = 1 / 0; //制造一个异常
u2.setMoney(u2.getMoney()+100);
userDao.save(u2);
}
}
- 对于函数1会正常转账
- 对于函数2转账失败,但是没有手动添加事务,会发生账户1改变,账户2没有改变的情况
- 对于函数3转账失败,但是手动添加了事务,发生异常时会回滚到转账前的状态