基本查询
基本查询也分为两种,一种是spring data默认已经实现,一种是根据查询的方法来自动解析成SQL。
预先生成方法
spring data jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等
1 继承JpaRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
2 使用默认方法
@Test
public void testBaseQuery() throws Exception {
User user=new User();
userRepository.findAll();
userRepository.findOne(1l);
userRepository.save(user);
userRepository.delete(user);
userRepository.count();
userRepository.exists(1l);
// ...
}
自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟属性名称:
User findByUserName(String userName);
也使用一些加一些关键字And、 Or
User findByUserNameOrEmail(String username, String email);
修改、删除、统计也是类似语法
Long deleteById(Long id);
Long countByUserName(String userName)
基本上SQL体系中的关键词都可以使用,例如:LIKE、 IgnoreCase、 OrderBy。
List<User> findByEmailLike(String email);
User findByUserNameIgnoreCase(String userName);
List<User> findByUserNameOrderByEmailDesc(String email);
具体的关键字,使用方法和生产成SQL如下表所示
方法 | 用法 |
---|---|
Keyword | Sample |
And | findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1 |
Between | findByStartDateBetween … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual … where x.age >= ?1 |
After | findByStartDateAfter … where x.startDate > ?1 |
Before | findByStartDateBefore … where x.startDate < ?1 |
IsNull | findByAgeIsNull … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull … where x.age not null |
Like | findByFirstnameLike … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) … where x.age not in ?1 |
TRUE | findByActiveTrue() … where x.active = true |
FALSE | findByActiveFalse() … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1) |
复杂查询
在实际的开发中我们需要用到分页、删选、连表等查询的时候就需要特殊的方法或者自定义SQL
分页查询
分页查询在实际使用中非常普遍了,spring data jpa已经帮我们实现了分页的功能,在查询的方法中,需要传入参数Pageable
,当查询中有多个参数的时候Pageable建议做为最后一个参数传入
Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);
Pageable 是spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则
@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
}
Sort类
导入org.springframework.data.domain.Sort;
Sort sort = new Sort(Sort.Direction.DESC,"mobile");
排序方式 | |
---|---|
Sort.Direction.DESC | 降序 |
Sort.Direction.ASC | 升序 |
限制查询
有时候我们只需要查询前N个元素,或者支取前一个实体。
ser findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
自定义SQL查询
在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等
@Modifying
@Query("update User u set u.userName = ?1 where c.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
多表查询
多表查询在spring data jpa中有两种实现方式,第一种是利用hibernate的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果,这里主要第二种方式。
public interface HotelSummary {
City getCity();
String getName();
Double getAverageRating();
default Integer getAverageRatingRounded() {
return getAverageRating() == null ? null : (int) Math.round(getAverageRating());
}
}
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")
Page<HotelSummary> findByCity(City city, Pageable pageable);
@Query("select h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r group by h")
Page<HotelSummary> findByCity(Pageable pageable);
使用
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));
for(HotelSummary summay:hotels){
System.out.println("Name" +summay.getName());
}
在运行中Spring会给接口(HotelSummary)自动生产一个代理类来接收返回的结果,代码汇总使用getXX的形式来获取
多数据源的支持
同源数据库的多源支持
日常项目中因为使用的分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因此需要配置sping data jpa对多数据源的使用,一般分一下为三步:
1 配置多数据源
2 不同源的实体类放入不同包路径
3 声明不同的包路径下使用不同的数据源、事务支持
这里有一篇文章写的很清楚:Spring Boot多数据源配置与使用
异构数据库多源支持
比如我们的项目中,即需要对mysql的支持,也需要对mongodb的查询等。
实体类声明@Entity 关系型数据库支持类型、声明@Document 为mongodb支持类型,不同的数据源使用不同的实体就可以了
interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
public class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
public class User {
…
}
但是,如果User用户既使用mysql也使用mongodb呢,也可以做混合使用
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
public class Person {
…
}
也可以通过对不同的包路径进行声明,比如A包路径下使用mysql,B包路径下使用mongoDB
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.neo.repositories.mongo")
interface Configuration { }
其它
使用枚举
使用枚举的时候,我们希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上面添加
@Enumerated(EnumType.STRING) 注解
@Enumerated(EnumType.STRING)
@Column(nullable = true)
private UserType type;
不需要和数据库映射的属性
正常情况下我们在实体类上加入注解@Entity,就会让实体类和表相关连如果其中某个属性我们不需要和数据库来关联只是在展示的时候做计算,只需要加上@Transient属性既可。
@Transient
private String userName;
JPA对象注解
- @Table - 映射表名
- @Id - 主键
- @GeneratedValue(strategy=GenerationType.IDENTITY) - 自动递增生成
- @Column(name = "dict_name",columnDefinition="varchar(100) COMMENT '字典名'") - 字段名、类型、注释
- @UpdateTimestamp - 更新时自动更新时间
- @CreationTimestamp - 创建时自动更新时间
- @Version - 版本号,更新时自动加1
用法:
@Entity
@Table(name="c_user")
public class User {
@Id
@GeneratedValue
private Long id;
@NotNull
@Column(length = 50)
private String userName;
@NotNull
@Column(length = 20 , unique = true, nullable = false)
private Long mobile;
@Column(length = 20 , unique = true)
private String email;
@NotNull
@Column(columnDefinition="tinyint")
private Integer status;
private String password;
private String nickname;
private Integer companyId;
private Integer departmentId;
private Date regTime;
private String regIp;
private Integer loginNum;
...
}
@Table
@Table注解用来标识实体类与数据表的对应关系,默认和类名一致。
@Column
@Column注解来标识实体类中属性与数据表中字段的对应关系。共有10个属性,这10个属性均为可选属性:
- name属性定义了被标注字段在数据库表中所对应字段的名称;
- unique属性表示该字段是否为唯一标识,默认为false。如果表中有一个字段需要唯一标识,则既可以使用该标记,也可以使用@Table标记中的@UniqueConstraint。
- nullable属性表示该字段是否可以为null值,默认为true。如果属性里使用了验证类里的@NotNull注释,这个属性可以不写。
- insertable属性表示在使用“INSERT”脚本插入数据时,是否需要插入该字段的值。
- updatable属性表示在使用“UPDATE”脚本插入数据时,是否需要更新该字段的值。insertable和updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的。
- columnDefinition属性表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时使用。若不指定该属性,通常使用默认的类型建表,若此时需要自定义建表的类型时,可在该属性中设置。(也就是说,如果DB中表已经建好,该属性没有必要使用。)
- table属性定义了包含当前字段的表名。
- length属性表示字段的长度,当字段的类型为varchar时,该属性才有效,默认为255个字符。
- precision属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数。
可以标注在getter方法或属性前:
标注在属性前:
@Column(name = " c_name ")
private String name;
public String getName() {
return name;
}
标注在getter方法前:
private String name;
@Column(name = " c_name ")
public String getName() {
return name;
}
例子:
指定字段“contact_name”的长度是“512”,并且值不能为null。
@Column(name="ct_name",nullable=false,length=512)
private String name;
指定字段“price”月收入的类型为double型,精度为12位,小数点位数为2位。
@Column(name="price",precision=12, scale=2)
private BigDecimal price;
字段的默认值
很简单声明的时候直接赋值即可:
@Column(columnDefinition="tinyint")
private Integer status = 1;
还有另外一种写法:
@Column(name="state", nullable=false, columnDefinition="tinyint default 0")
private Interger state;
双表查询
public class Post {
private long id;
private String title;
private String content;
@ManyToOne
private User creator;
...
}
public class User {
private long id;
private String email;
private String userName;
...
}
此时,我们知道,Post表中creator字段存储的是User的id。通常情况下,我们可以根据User对象来获取某个用户创建的所有博客:
List<Post> findByCreator(User user);
但是在这种方式下,如果我们只知道用户的userName,我们就必须通过UserRepository先拿到User对象,再调用上述方法得到某个用户创建的所有博客。
实际上,我们通常会通过连表查询的方式一次性拿到相应的数据。在JPA里面,通常有下面两种方法:
通过方法名定义
我们可以通过如下语句去定义一个连表查询,
List<Post> findByCreatorUserName(String userName);
此时,Spring Data JPA将按照以下顺序创建查询语句:
首先查找Post对象中是否有creatorUserName属性,如果有,则直接生成查询语句… where x.creatorUserName = ?1,我们的Post对象并没有该属性,进入下一步
查找是否含有creatorUser属性,如果有,则看creatorUser是否含有name属性,如果有,则生成相应的查询语句,我们的Post对象并没有该属性,进入下一步
查找是否有creator属性,如果依然没有,则抛出错误PropertyReferenceException,我们的Post对象存在该属性,进入下一步
查找creator是否含有userName属性,若没有,依然抛出PropertyReferenceException,我们的User对象存在该属性,进入下一步
由于creator存在userName属性,生成查询语句… where x.creator.userName = ?1
我们可以看到,属性表达式通过字母的大小写来与实体对象的属性进行关联。但是,如果在Post对象中恰巧有creatorUserName这个属性,那么,Spring Data JPA将会在第一步就完成查询语句的生成,但是,这并不是我们所希望的结果。此时,我们可以通过_来控制属性表达式:
List<Post> findByCreator_UserName(String userName);
此时,Spring Data JPA的属性表达式将直接寻找creator属性下的userName属性。
通过@Query标注定义
在JPA中,实际上是可以自己写sql语句的,方法如下:
@Query("select * from post inner join user on post.creator = user.id where user.userName = ?1;", nativeQuery = true)
List<Post> findByUser(String userName);
多表查询
1.目的:记录如何 用jpa来实现快速的多表查询, 暂不深入探究jpa的内部实现
2. 说明情形:
@1所涉及的表为t_user(id,name,date,...);t_factory_user(id,factory_id,user_id,...), 其中表s_factory_user[user_id]==t_user[id], 为主外键的关联关系
@2现状 在entity类User中, 如下代码:
@OneToMany(mappedBy = "s_factory_user", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private FactoryUser fu = new FactoryUser();
在entity类FactoryUser中, 如下代码:
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH})
@JoinColumn(name = "user_id")
private User user;
添加代码后, 在启动项目后会报错, “org.hibernate.MappingException: Could not determine type for:”, 即为在不能在Use实体类中,定义属性fu(fu属性类型为factoryUser);
解决:在查询了相关文章后, “mappingexception”是指配置hibernate配置文件时错误或没有加载上hbm配置文件。现在该项目中用到的是jpa的注解方式来配置hibernate映射关系,还是我在属性的注解上有错误。 尝试直接在属性上添加属性@Transient, 加载依然报错,那么现在简单了解一下@OneToMany的使用;
@1.其中的mappedBy指的是关联类FactoryUser中的属性名user
@2. 在factoryUser类中,@JoinColumn中的name属性为‘userId’, 是对应的factoryUser的属性userId
则正常的应该为:
在factoryUser类中
private User user;
@OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH})
@JoinColumn(name = "userId")
public User getUser() {
return user;
}
在User类中
private FactoryUser fu;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
public FactoryUser getFu() {
return fu;
}
ok, 这样项目可以正常启动了。
3.使用jpa的查询
@1 要集成接口类JpaSpecificationExecutor, 该类提供了几个方法
T findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
@2 具体的使用
<span> </span>Specification<User> spec = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate p1 = cb.equal(root.get("userCategory").as(Integer.class), 2);
// 设置sql链接
Join<User, FactoryUser> fuJoin = root.join(root.getModel().getSingularAttribute("fu", FactoryUser.class), JoinType.INNER);
Predicate p2 = cb.equal(fuJoin.get("factoryId").as(Long.class), factoryIdFinal);
query.where(cb.and(p1, p2));
// 添加排序的功能
query.orderBy(cb.desc(root.get("id").as(Long.class)));
return query.getRestriction();
}
};
return userDao.findAll(spec);