• 一种标准,本质上是一种ORM规范,注意不是ORM框架,因为它只是提供了一些接口,至于如何实现则由服务厂商来提供。它是是JDK5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
  • 持久化:持久化是将程序数据再持久状态和瞬间状态转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中),能够持久保存。
  • 持久化是一种对象服务,就是把内存中的对象保存到外存中,让它以后能够取回。
  • 为什么需要持久化服务呢?那是由于内存本身的缺陷引起的:
    • 内存掉电后数据会丢失
    • 内存过于昂贵,与硬盘、磁带、光盘等外存相比,内存的价格要搞2~3个数量级,而且维持成本也高,至少需要一直供电;而且容量有限制。
  • 实现jpa标准的框架有Hibernate等。
  • JpaRepository的一些接口
  • 里面有一张表
  • 其中save方法:记录不存在则插入,存在则更新。

@Query自定义查询语句

  • 用@Query直接在方法上定义查询语句
public interface TaskDao extends JpaRepository<Task,Long>{
    @Query("select t from Task t where t.taskName=?1")//1表示这是第一个参数
    Task findByTaskName(String taskName);
}
  • 除了写hql,我们还可以写sql语句
public interface TaskDao extends JpaRepository<Task, Long> {
    @Query("select * from tb_task t where t.task_name = ?1", nativeQuery = true)
    Task findByTaskName(String taskName);
}
  • 传参也可以这样传:
public interface TaskDao extends JpaRepository<Task, Long> {
    @Query("select t from Task t where t.taskName = ? and t.createTime = ?")
    Task findByTaskName(String taskName, Date createTime);
}
  • 还可以这样传:
public interface TaskDao extends JpaRepository<Task, Long> {
    @Query("select t from Task t where t.taskName = :taskName and t.createTime = :createTime")
    Task findByTaskName(@Param("taskName")String taskName,@Param("createTime") Date createTime);
}
  • 自定义查询语句+分页效果
/**
 * 模糊查询
 **/
@Query("from Document d where d.author like %?1% or content like %?1% or title like %?1%")
Page<Document> findByOneKey(String key,Pageable pageable);

//测试方法
Page<Document> documents=documentRepository.findByOneKey("haien",new PageRequest(0,10, Sort.Direction.DESC,"createDate"));
  • 我们可以利用SqEL表达式,把实体类写成动态的:
    public interface TaskDao extends JpaRepository<Task, Long> {
         @Query("select t from #{#entityName} t where t.taskName = ? and t.createTime = ?")
        Task findByTaskName(String taskName, Date createTime);
    }
- 其中#{#entityName}表示获取entityName的值,即实体类的名称。
- 实体类Task在使用@Entity注释后,Spring会将它纳入管理,若未指定表名,则entityName的值为Task,指定了则为指定值。
- Spring管理着多个实体类,谁调用了这个方法entityName就等于哪个实体类。
  • 它的作用是,当两个实体类都有共同的父类时,可以抽取出一个通用repository
  • 首先定义一个父类
// JPA 基类的标识
@MappedSuperclass //被注释的实体类不映射到数据库,但其子类会
@SuppressWarnings("serial") //抑制编译器的警告(比如不要飘波浪线、小灯泡),括号里的值表示警告类型;可以设为unchecked来逃避异常处理
public abstract class IdEntity implements Serializable{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}
  • 两个子类
@Entity
public class Task extends IdEntity{

}

@Entity
public class Project extends IdEntity{

}
  • 然后有一个通用的接口:
@NoRepositoryBean //此接口无对应的数据库,不能暴露为repository
public interface GenericDao<T> extends JpaRepository<T, ID> { 
    @Query("select t from #{#entityName} t where t.id= ?1") //entityName是个特有名词,传进来什么实体就用什么代替
    public T findById(Long id);   
}
  • 然后由taskDao和projectDao来继承这个接口。这样,把公用的方法放在通用接口上,就不用重写方法了。
public interface TaskRepository extends GenericDao<Task> {

}

public interface ProjectRepository extends GenericDao<Project>{

}
  • 测试
@Test
public void findByIdTest(){
    Project project=new Project();
    project.setId(1);
    projectRepository.save(project);
    project=projectRepository.findById(1);
    Assert.assertThat(project.getId(),is(1));
}
  • 携带分页信息
//dao层
@Query("select u from User u where u.name=?1")
public List<User> findByName(String name, Pageable pageable);

//service层调用
@RequestMapping(value = "/params", method= RequestMethod.GET)
@ResponseBody
public String getEntryByParams(@RequestParam(value = "name", defaultValue = "林志强") String name, @RequestParam(value = "page", defaultValue = "0") Integer page, @RequestParam(value = "size", defaultValue = "15") Integer size) {
    Sort sort = new Sort(Sort.Direction.DESC, "id");
    Pageable pageable = new PageRequest(page, size, sort);
    Page<User> pages=userDao.findByName(name,pageable);
    Iterator<User> it=pages.iterator();
    while(it.hasNext()){
        System.out.println("value:"+((User)it.next()).getId());
    }
    return "success...login....";
}

@Modifying标识自定义更新语句

@Modifying //表明是更新语句
@Transactional //一定要有事务,不是在这里就是在service层,反正那个会被直接调用那个就必须带事务
@Query("update Task t set t.taskName = ?1 where t.id = ?2")
int updateTask(String taskName, Long id);

@Modifying
@Transactional
@Query("delete from Score where model = ?1 and year = ?2")
void deleteByModelAndYear(String model,int year);

@Modifying
@Transactional
@Query(value = "insert into users(username,password,update_time,people_id) value(?1,SUBSTRING(md5(?2),1,16),?3,?4)",nativeQuery = true)
int saveUsers(String username, String password, Date update_time, int peopleId);
  • 返回值只能是int或void
  • jpa要求更新、删除和插入操作必须有事务支持

自定义数据库操作语句汇总

  • 反正宗旨就是利用原生sql命令来实现那些复杂的关联查询

@Transactional事务

  • 操作数据库过程中可能发生异常,导致后续操作无法完成。此时需要进行回滚。
  • Spring默认会对没有被捕获(unchecked)的RunTimeException进行事务回滚,如果异常已经被catch也即是遇到的事checked异常的话则不回滚。
  • 改变默认规则的话:
    • 让原本不能回滚的checked回滚:在方法前加@Transaction(rollbackFor=Exception.class/rollbackForClassName=Exception)
    • 让原本会回滚的unchecked不回滚:@Transaction(notRollbackFor=RunTimeException.class/notRollbackForClassName=Exception)
    • PS:如果原本能回滚的异常被try/catch了还想让它回滚,那就必须再抛出一个异常。
  • 但实践证明,在测试中使用事务,无论是否出现异常,都会自动回滚,数据库会保持和测试前一致。
  • jpa那些默认的接口都没有默认开启事务的,只是支持事务。
  • 要开启事务的话,可以在service类前加@Transactional,声明这个service的所有方法都需要事务管理。
  • 或者是在测试类的测试方法前加@Transactional,声明这个方法里调用到的操作数据库的方法需要事务管理。加了的话测试类无论抛异常与否都会自动回滚,防止数据库污染。
  • 如果加了该注解后,测试类没有自动回滚,可查看数据库引擎是否为Innodb,因为其他数据库引擎不支持事务。
  • 也可以在dao层的Repository接口里创建操作数据库的方法前加上@Transactional,声明此方法开启事务管理(不过这种情况应该是很少的,因为查询方法一般都是单操作,没什么意义,可以参考HPScore/ScoreRepository)。
  • 该注解只能加在public方法上,加在其他方法上无效。
  • 代码实例:HPScore/test/controller/ResolveExcelControllerTest
value参数
  • 以上使用的是默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如: @Transactional(value=”transactionManagerPrimary”) 。

另一种自定义查询、构建通用dao的方法

  • 这种自定义查询方法是自己写一个类似JpaRepository的仓库来被实体类的仓库继承
  • 我们先创建一个将被继承的BaseRepository
//repository 基类,封装自定义查询方法
@NoRepositoryBean //该注解表示 spring 容器不会创建该对象
public interface BaseRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>,JpaRepository<T,ID> {

    Page<Map> findPageByParams(String sql, Pageable pageable, Object... args);

}
  • 创建BaseRepositoryImpl类,实现BaseRepository接口
public class BaseRepositoryImpl<T, ID extends Serializable> 
    extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

    //实体管理类,对持久化实体做增删改查,自动义sq操作模板所需要的核心类
    public final EntityManager entityManager;

    public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
            EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Page<Map> findPageByParams(String sql, Pageable pageable, Object... args) {
        Session session = (Session) entityManager.getDelegate();
        org.hibernate.Query q = session.createSQLQuery(sql);
        q.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
        int i = 0;
        for (Object arg : args
                ) {
            q.setParameter(i++, arg);
        }

        List<Map> totalCount = q.list(); //如果不需要分页的话这样就可以返回去了
        //每页第一个
        q.setFirstResult(pageable.getPageSize() * (pageable.getPageNumber() - 1)); 
        q.setMaxResults(pageable.getPageSize()); //每页容量
        List<Map> pageCount = q.list();
        Page<Map> pages = new PageImpl<>(pageCount, pageable, totalCount.size());
        return pages;
    }

}
  • 创建一个PersonRepositroy 继承BaseRepository,就可以直接使用定义的查询方法了
public interface PersonRepository extends BaseRepository<Person, Integer> {

}
  • TestController
@RestController 
public class TestController  {

    @Resource
    private PersonRepository personRepository;

    @GetMapping(value = "/test")
    public String test( ){

        //订单表与用户表关联,通过用户ID查询该用户的所有订单,只获取订单编号和订单详情。
        String sql="select o.no,o.detail from person as p inner join order as o 
            on o.personid=p.id and p.id= ? "
        Integer userId=11;
        Page<Map>  orderLists = personRepository.findPageByParams(sql,
            new PageRequest(1,10),userId);

        return   orderLists;
    }
}
  • 最后,在项目启动类上加一个注解,告诉springboot,我要使用JPA,请到repository包下去扫描我创建的repository 类,我的repository基类是BaseRepositoryImpl
@EnableJpaRepositories(basePackages = {"com.repository"}, 
    repositoryBaseClass = BaseRepositoryImpl.class)
public class MybootApplication {

    public static void main(String[] args) {
            SpringApplication.run(MybootApplication.class, args);
        }

}

还有一种自定义查询、构建通用dao的方法

  • 先构建一个通用dao接口
public interface BaseAppDAO<T,ID extends Serializable> {
    /**
     * 保存数据对象
     * @param entity
     * @return
     */
    boolean save(T entity);
    /**
     * 根据id查询
     * @param id
     * @param t
     * @return
     */
    T findByid(T t,Long id);
    /**
     * 根据表名,字段,参数查询,拼接sql语句
     * @param  tablename 表名
     * @param filed 字段名
     * @param o 字段参数
     * @return
     */
    List<T> findBysql(String tablename,String filed,Object o);
    Object findObjiectBysql(String tablename,String filed,Object o);

    /**
     * 多个字段的查询
     * @param tablename 表名
     * @param map 将你的字段传入map中
     * @return
     */
    List<T> findByMoreFiled(String tablename,LinkedHashMap<String,Object> map);

    /**
     * 多字段查询分页
     * @param tablename 表名
     * @param map 以map存储key,value
     * @param start 第几页
     * @param pageNumer 一个页面的条数
     * @return
     */
    List<T> findByMoreFiledpages(String tablename, LinkedHashMap<String,Object> map, 
        int start, int pageNumer);
    /**
     * 一个字段的分页
     * @param  tablename 表名
     * @param filed 字段名
     * @param o 字段参数
     * @param start 第几页
     * @param pageNumer 一个页面多少条数据
     * @return
     */
    List<T> findpages(String tablename,String filed,Object o,int start,int pageNumer);
    /**
     * 根据表的id删除数据
     * @param  entity
     */
    boolean delete(T entity);
    /**
     * 更新对象
     * @param e
     * @return
     */
    boolean update(T e);
    /**
     * 根据传入的map遍历key,value拼接字符串,以id为条件更新
     * @param tablename 表名
     * @param map 传入参数放入map中
     * @return
     */
    Integer updateMoreFiled(String tablename,LinkedHashMap<String,Object> map);


    /**
     * 根据条件查询总条数返回object类型
     * @param tablename  表名
     * @param map 传入参数放入map中
     * @return
     */
    Object findCount(String tablename, LinkedHashMap<String,Object> map);
}
  • 实现dao接口
@Repository
public class BaseAppDAOimpl<T,ID extends Serializable> implements BaseAppDAO<T,ID> {

    @PersistenceContext //由EJB容器动态注入EntityManager对象
    private EntityManager entityManager;

    @Transactional
    @Override
    public boolean save(T entity){
        boolean flag=false;
        try {
            entityManager.persist(entity); //persist():保存实体bean
            flag=true;
        }catch (Exception e){
            System.out.println("---------------保存出错---------------");
            throw e;
        }
        return flag;
    }

    @Transactional
    @Override
    public Object findByid(Object o,Long id) {
        return entityManager.find(o.getClass(),id); //查找方法,传入实体类型和主键
    }

    @Transactional
    @Override
    public List<T> findBysql(String tablename, String filed, Object object ) {
        String sql="from "+tablename+" u WHERE u."+filed+"=?"; //动态指定表
        System.out.println(sql+"--------sql语句-------------");
        Query query=entityManager.createQuery(sql); //执行sql语句,返回Query对象
        query.setParameter(1,object); //按坑位设置参数
        List<T> list= query.getResultList();
        entityManager.close();
        return list;
    }

    @Override
    public Object findObjiectBysql(String tablename, String filed, Object o) {
        String sql="from "+tablename+" u WHERE u."+filed+"=?";
        System.out.println(sql+"--------sql语句-------------");
        Query query=entityManager.createQuery(sql);
        query.setParameter(1,o);

        entityManager.close();
        return query.getSingleResult(); //获取单个结果,需确保结果一定唯一
    }

    @Transactional
    @Override
    public List<T> findByMoreFiled(String tablename,LinkedHashMap<String,Object> map) {
        String sql="from "+tablename+" u WHERE ";
        Set<String> set=null;
        set=map.keySet(); //获取所有key,即字段名
        List<String> list=new ArrayList<>(set); //将字段名转换为有序链表
        List<Object> filedlist=new ArrayList<>(); //准备将字段名转移到该数组
        for (String filed:list){
            sql+="u."+filed+"=? and "; //拼接sql语句
            filedlist.add(filed); //转移字段名(原因不清)
        }
        sql=sql.substring(0,sql.length()-4); //去掉多出的字符
        System.out.println(sql+"--------sql语句-------------");
        Query query=entityManager.createQuery(sql); 
        for (int i=0;i<filedlist.size();i++){
            //设置参数,根据字段名获取值
            query.setParameter(i+1,map.get(filedlist.get(i))); 
        }
        List<T> listRe= query.getResultList();
        entityManager.close();
        return listRe;
    }

    @Transactional
    @Override
    public List<T> findByMoreFiledpages(String tablename,
            LinkedHashMap<String,Object> map,int pageNum,int size) { 
            //分页,每请求一页执行一次

        String sql="from "+tablename+" u WHERE ";
        Set<String> set=null;
        set=map.keySet();
        List<String> list=new ArrayList<>(set);
        List<Object> filedlist=new ArrayList<>();
        for (String filed:list){
            sql+="u."+filed+"=? and ";
            filedlist.add(filed);
        }
        sql=sql.substring(0,sql.length()-4);
        System.out.println(sql+"--------sql语句-------------");
        Query query=entityManager.createQuery(sql);
        for (int i=0;i<filedlist.size();i++){
            query.setParameter(i+1,map.get(filedlist.get(i)));
        }
        query.setFirstResult((pageNum-1)*size); //设置第一条数据
        query.setMaxResults(size); //设置每页容量
        List<T> listRe= query.getResultList();
        entityManager.close();
        return listRe;
    }

    @Transactional
    @Override
    public List<T> findpages(String tablename, String filed, Object o, int start, 
            int pageNumer) {
        String sql="from "+tablename+" u WHERE u."+filed+"=?";
        System.out.println(sql+"--------page--sql语句-------------");
        List<T> list=new ArrayList<>();
        try {
            Query query=entityManager.createQuery(sql);
            query.setParameter(1,o);
            query.setFirstResult((start-1)*pageNumer);
            query.setMaxResults(pageNumer);
            list= query.getResultList();
            entityManager.close();
        }catch (Exception e){
            System.out.println("------------分页错误---------------");
        }

        return list;
    }
    @Transactional
    @Override
    public boolean update(T entity) {
        boolean flag = false;
        try {
            entityManager.merge(entity);
            flag = true;
        } catch (Exception e) {
            System.out.println("---------------更新出错---------------");
        }
        return flag;
    }

    @Transactional
    @Override
    public Integer updateMoreFiled(String tablename, LinkedHashMap<String, Object> map) 
    {
        String sql="UPDATE "+tablename+" AS u SET ";
        Set<String> set=null;
        set=map.keySet();
        List<String> list=new ArrayList<>(set);
        for (int i=0;i<list.size()-1;i++){
            if (map.get(list.get(i)).getClass().getTypeName()=="java.lang.String"){
                System.out.println("-*****"+map.get(list.get(i))+
                    "------------"+list.get(i));
                sql+="u."+list.get(i)+"='"+map.get(list.get(i))+"' , ";
            }else {
                sql+="u."+list.get(i)+"="+map.get(list.get(i))+" , ";
            }
        }
        sql=sql.substring(0,sql.length()-2);
        sql+="where u.id=? ";
        System.out.println(sql+"--------sql语句-------------");
        int resurlt=0;
        try {
            Query query=entityManager.createQuery(sql);
            query.setParameter(1,map.get("id"));
            resurlt= query.executeUpdate();
        }catch (Exception e){
            System.out.println("更新出错-----------------------");
            e.printStackTrace();

        }
        return resurlt;
    }

    @Transactional
    @Override
    public boolean delete(T entity) {
        boolean flag=false;
        try {
            entityManager.remove(entityManager.merge(entity));
            flag=true;
        }catch (Exception e){
            System.out.println("---------------删除出错---------------");
        }
        return flag;
    }

    @Override
    public Object findCount(String tablename, LinkedHashMap<String, Object> map) {
        String sql="select count(u) from "+tablename+" u WHERE ";
        Set<String> set=null;
        set=map.keySet();
        List<String> list=new ArrayList<>(set);
        List<Object> filedlist=new ArrayList<>();
        for (String filed:list){
            sql+="u."+filed+"=? and ";
            filedlist.add(filed);
        }
        sql=sql.substring(0,sql.length()-4);
        System.out.println(sql+"--------sql语句-------------");
        Query query=entityManager.createQuery(sql);
        for (int i=0;i<filedlist.size();i++){
            query.setParameter(i+1,map.get(filedlist.get(i)));
        }
        return query.getSingleResult();
    }
}

Spring Data JPA

  • 是在JPA规范的基础下提供Repository层的实现,但是具体使用哪一款ORM框架需要你自己决定。也就是说,虽然ORM框架都实现了JPA规范,但是在不同的ORM框架之间切换,编写的代码是有一定差异的,而通过使用Spring Data JPA能够方便大家在不同的ORM框架中间进行切换而不需要更改代码