Spring Data Jpa 复杂查询方式总结(多表关联及自定义分页)
作者:ZhuPengWei_
Spring Data Jpa复杂查询总结
只是做一个总结所以就不多说废话了
实体类
@Entity @Table(name = "t_hotel") @Data public class THotel { @Id private int id; private String name; private String address; /** * 城市id */ private String city; }
@Entity @Table(name = "t_city") @Data public class TCity { @Id private int id; private String name; private String state; private String country; private String map; }
在启动SpringBoot的时候 SpringDataJpa会自动的在数据库中生成表结构.
为了查询要求,我随便的增加了一些数据,如下图所示
新建接口
public interface TCityRepository extends JpaRepository<TCity, Integer>, JpaSpecificationExecutor<TCity> { }
单元测试类
@RunWith(SpringRunner.class) @SpringBootTest public class TCityRepositoryTest{ @Autowired private TCityRepository tCityRepository; }
1.查找出Id小于3,并且名称带有shanghai的记录
/** * 查找出Id小于3,并且名称带有`shanghai`的记录. * * @param id id * @param name 城市名称 * @return 城市列表 */ List<TCity> findByIdLessThanAndNameLike(int id, String name);
单元测试
@Test public void findByIdLessThanAndNameLike() throws Exception { List<TCity> shanghai = tCityRepository.findByIdLessThanAndNameLike(3, "%shanghai%"); Assert.assertTrue(shanghai.size() > 0); }
2.通过旅店名称分页查询旅店以及城市的所有信息
/** * 通过旅店名称分页查询旅店以及城市的信息 * * @param name 旅店名称 * @param pageable 分页信息 * @return Page<Object[]> */ @Query(value = "select t1.name as cityName,t2.name as hotelName\n" + "from t_city t1\n" + " left join t_hotel t2 on t2.city = t1.id\n" + "where t2.name = :name", countQuery = "select count(*)" + "from t_city t1 \n" + " left join t_hotel t2 on t2.city = t1.id\n" + "where t2.name = :name" , nativeQuery = true) Page<Object[]> findCityAndHotel(@Param("name") String name, Pageable pageable);
为了节约时间 我只在select 与 from 之间 分别查询了城市的名称以及旅店的名称如果要查所有的信息,可以换成t1.* ,t2.*
单元测试
@Test public void findCityAndHotel() throws Exception { Page<Object[]> cityAndHotel = tCityRepository.findCityAndHotel("酒店", new PageRequest(0, 10)); Assert.assertTrue(cityAndHotel.getTotalElements() > 0); }
关于把List<Object[]> 转换成List<对象> 的方法 我已经在上一篇JPA的文章中比较清楚的说了,因此我就不再重复的叙述一遍了
3.HQL通过旅店名称查询旅店以及城市的所有信息
3和2其实是一样的,为了方便我就不作出分页查询了
HQL可以用map来接受返回的参数,具体的用法如下所示:
/** * HQL通过旅店名称查询旅店以及城市的所有信息 * * @return */ @Query(value = "select new map(t1,t2) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name =:name") List<Map<String, Object>> findCityAndHotelByHQL(@Param("name") String name);
测试方法和2是差不多的 我就不粘贴了
Map<String.Object> 转换实体类的方法也挺多的我就不多说了,如果是直接返回给前台的话,也没有必要转换成对象.
4.HQL通过旅店名称查询旅店以及城市的所有信息 直接返回实体类
/** * 关联查询 * * @return */ @Query(value = "select new pers.zpw.domain.CityHohel(t1.name AS cityName,t2.name AS hotelName) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name =:name") List<CityHohel> findCityAndHotelByHQLResultObj(@Param("name") String name);
为了方便CityHohel我只封装了2个属性,这和HQL查询的字段是完全一致的,也必须要保持一致.
/** * Created by ZhuPengWei on 2018/5/12. */ @Data public class CityHohel { private String cityName; private String hotelName; public CityHohel(String cityName, String hotelName) { this.cityName = cityName; this.hotelName = hotelName; } }
当然这个带参的构造方法是必须要写的,否则会抛出转换实体的异常
单元测试
@Test public void findCityAndHotelByHQLResultObj() throws Exception { List<CityHohel> cityAndHotelByHQLResultObj = tCityRepository.findCityAndHotelByHQLResultObj("酒店"); Assert.assertTrue(cityAndHotelByHQLResultObj.size() > 0); }
4.HQL通过旅店名称分页查询旅店以及城市的所有信息 直接返回实体类
/** * 关联查询 * * @return */ @Query(value = "select new pers.zpw.domain.CityHohel(t1.name AS cityName,t2.name AS hotelName) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name =:name", countQuery = "select count(*) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name =:name") Page<CityHohel> findCityAndHotelAllSelf(@Param("name") String name, Pageable pageable);
@Test public void findCityAndHotelAllSelf() throws Exception { Page<CityHohel> cityAndHotelAllSelf = tCityRepository.findCityAndHotelAllSelf("酒店", new PageRequest(0, 10)); Assert.assertTrue(cityAndHotelAllSelf.getTotalElements() > 0); }
5.动态查询旅店以及城市的所有信息 直接返回实体类
如果是动态查询的话当然首先得构造一条sql去查询了,当然如果不是自定义实体对象的话这样的网上一大堆我就不写了.
直接走测试
@Autowired @PersistenceContext private EntityManager entityManager; @Test public void testDynamic() throws Exception { String sql = "select new pers.zpw.domain.CityHohel(t1.name AS cityName,t2.name AS hotelName) from TCity t1 left join THotel t2 on t1.id=t2.city where t2.name ='酒店'"; Query query = entityManager.createQuery(sql); List resultList = query.getResultList(); Assert.assertTrue(resultList.size() > 0); }
这样测试是通过的,因此可以知道在业务层的方法中我们可以动态的构造SQL语句. 比如说可以在接口中这样子来定义一个方法
/** * 自定义查询 * @param sql * @param entityManager * @return */ default List customQuery(String sql, EntityManager entityManager) { return entityManager.createQuery(sql).getResultList(); }
然后在测试类中动态的根据条件去拼接SQL语句去调用
JPA#复杂查询#自定义查询
编写自定义SQL基于下面信息
1. SpringData JPA 在为Repository接口生成实现的时候,会查找是否有 "接口名称"+"Impl"的类,如果有的话,就把这个类的方法合并到要生成的实现当中。
假设:要为接口StudentRepository编写自定义sql查询。
基于最前面的信息,要编写自定义SQL
需要下面三步:
1. 自定义一个接口,在在接口中声明方法StudentCoustomRepository,这个自定义接口名称不重要;
2. 让目标接口继承自定义接口,这样目标接口就有了相应的方法;
3. 编写自定义方法的实现类,这个类需要使用"目标接口名称"+"Impl"为类名,
即StudentRepositoryImpl,这样SpringDataJpa 为StudentRepository生成实现的时候就会包含这里面的方法了。
public class StudentRepositoryImpl implements StudentCoustomRepository { // 这里可以写很复杂的SQL,作为演示之用,就不弄那么复杂 private static final String SQL = "select * from t_student where name like :name "; @PersistenceContext private EntityManager em; @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public List<StudentVO> findByName(String name) { Query query = em.createNativeQuery(SQL).setParameter("name", "%"+name+"%"); query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); List queryList = query.getResultList(); if (queryList.size() == 0) { System.out.println("找不到Student name为" + name + "的记录"); return null; } List<StudentVO> retVal = new ArrayList<>(); for(Object o : queryList) { Map student = (Map)o; StudentVO vo = new StudentVO(); try { // org.apache.commons.beanutils.BeanUtils; // 使用apaches的beanutil,直接吧map转为实例 BeanUtils.populate(vo, student); retVal.add(vo); } catch (IllegalAccessException | InvocationTargetException e) { System.out.println("解析StudentVO bean时发生异常:" + e.getMessage()); } } return retVal; } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。