spring data jpa 查询自定义字段,转换为自定义实体方式
目标:查询数据库中的字段,然后转换成 JSON 格式的数据,返回前台。
环境:idea 2016.3.4, jdk 1.8, mysql 5.6, spring-boot 1.5.2
背景:首先建立 entity 映射数据库(非专业 java 不知道这怎么说)
@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String userName; // 账号 private String password; // 密码 // getter setter 方法略过 }
然后建立与之对应的 model
public class UserModel implements Serializable { // 一些属性 }
这里我们分情况讨论
首先第一种情况:
查询的字段与表中的字段全部对应(就是查表里所有的字段,但是使用 Model 作为接收对象)
这种情况比较简单,调用 Repository 提供的方法,返回一个 entity , 然后将 entity 的属性复制到 model 中。像这样
UserModel user = new UserModel(); User userEntity = new User(); // 一个工具类,具体使用方法请百度 BeanUtils.copyProperties(user, userEntity);
第二种情况:只查询指定的几个字段
现在我有张表,有字段如下:
@Entity @Table(name = "user_info") public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name = "用户"; // 昵称 private String signature; // 个性签名 private String gender = "未知"; // 性别 private String description; // 个人说明 private String avatar; // 头像 private Long role; // 权限 private Boolean disable; // 是否冻结 private Date createTime; // 创建时间 private Boolean isDelete; // 是否删除 private Long userId; // 用户 Id // ... }
但是我只需要查询指定的几个字段,然后转换成 JSON,返回给前台,咋办呢?
第一种方法:使用 model 查询时转化
首先建立一个 model ,写上自己想要查询的字段,然后写上构造函数,这步很重要,因为spring jpa 转化时会调用这个构造方法
public class MyModel implements Serializable { private String userName; private String name; private String gender; private String description; public MyModel() {}; public MyModel(String userName, String name, String gender, String description) { this.userName = userName; this.name = name; this.gender = gender; this.description = description; } }
然后在 dao 类中写查询方法
@Query(value = "select new pers.zhuch.model.MyModel(u.userName, ui.name, ui.gender, ui.description) from UserInfo ui, User u where u.id = ui.userId") public List<MyModel> getAllRecord();
直接在查询语句中 new model 框架底层会调用它,然后返回这个对象(这里我写了完整的类路径,不写的时候它报错说找不到类型什么的)
然后就可以获得只有指定字段的 model 了。然后就把它转成 JSON 格式就 O 了。
第二种方法:在service 里边转换成 JSON
原理其实和第一种方法差不多,只是处理结果的方式不太一样,只是这种方法我们就不在 hql 中 new Model 了,直接写查询方法
@Query(value = "select new map(u.userName, ui.name, ui.gender, ui.description) from UserInfo ui, User u where u.id = ui.userId") public List<Map<String, Object>> getCustomField();
直接new map(这里得是小写,不知道大写有木有问题,反正没试,编译器提示是要小写的)
然后返回的结果是这样的
[
{
"0": "admin",
"1": "你猜",
"2": "男",
"3": "一段描述"
}, {
"0": "abc",
"1": "你猜人家",
"2": "女",
"3": "没事先挂了"
}
]
然后在 service 层里直接封装成 JSON 对象,返回
List<JsonObject> list = new ArrayList(); for(Map map : result) { JsonObject j = new JsonObject(); j.addProperty(attrName, val); ... list.add(j); } gson.toJson(list);
还有一种返回结果,这样写:
@Query(value = "select u.userName, ui.name, ui.gender, ui.description from UserInfo ui, User u where u.id = ui.userId") public List<Object> getCustomField();
返回结果是这样的格式:
[
[
"admin",
"你猜",
"男",
"一段描述"
], [
"abc",
"你猜人家",
"女",
"没事先挂了"
]
]
返回的是数组,也一样可以通过上面的方法转成 json ,这里我的程序中出现了一点点 BUG,就是空值的字段不会在数组中,不知道为什么。
这种方法必须明确的知道查询了哪些字段,灵活性比较差,虽然它解决了手头的问题。还有就是版本的不同,有可能会出现丢失空字段的情况,我个人特别的不喜欢这样的方法,万一我实体几十个字段,写着写着忘了写到哪了,就 over 了
第三种方法:返回一个便于转换成 json 格式的 list
其实和上面很相似,都是 dao 层返回一个 List < Map < String, Object >>,但是上面的结果集返回的 Map 的 key 只是列的下标,这种方式稍微理想一点点,就是 Map 的 key 就是查询的列名。
但是这种方式需要实现自定义 Repository( 这里不详细介绍,请自行百度 ),并且只是 jpa 集成 hibenate 的时候可以使用。
public List getCustomEntity() { String sql = "select t.id, t.name, t.gender, t.is_delete, t.create_time, t.description from t_entity t"; Query query = em.createNativeQuery(sql); // Query 接口是 spring-data-jpa 的接口,而 SQLQuery 接口是 hibenate 的接口,这里的做法就是先转成 hibenate 的查询接口对象,然后设置结果转换器 query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); return query.getResultList(); }
这种方法返回的就是比较标准的 JSON 格式的 java 对象了,只需要用 jackson 或者 Gson 转一下就是标准的 json 了
[
{
attr: val,
...
},
{
attr: val,
...
},
]
这种方式其实已经比较理想了,因为直接就能返回到前台,但是有时候,结果不是一条 sql 能够解决的,得两条或者以上的 sql 来解决一个复杂的查询需求,这个过程中,结果比较需要转换成 pojo,以便于组装操作。
第四种方案:dao 中直接转成 pojo 返回
这个方案还是依赖于 hibenate,有点操蛋,但是更明确一些。
public List getCustomEntity() { String sql = "select t.id, t.name, t.gender, t.is_delete as isEnable, t.create_time as createTime, t.description from t_entity t"; Query query = em.createNativeQuery(sql); query.unwrap(SQLQuery.class) // 这里是设置字段的数据类型,有几点注意,首先这里的字段名要和目标实体的字段名相同,然后 sql 语句中的名称(别名)得与实体的相同 .addScalar("id", StandardBasicTypes.LONG) .addScalar("name", StandardBasicTypes.STRING) .addScalar("gender", StandardBasicTypes.STRING) .addScalar("isEnable", StandardBasicTypes.BOOLEAN) .addScalar("createTime", StandardBasicTypes.STRING) .addScalar("description", StandardBasicTypes.STRING) .setResultTransformer(Transformers.aliasToBean(EntityModel.class)); return query.getResultList(); }
这次返回的就是 List 了。这里要注意的是 StandardBasicTypes这个常量类,在一些旧版本中,是 Hibenate 类,具体哪个包我不知道,我这个版本中是换成了前面的那个常量类
继承jpa Repository 写自定义方法查询
今天在写jpa查询的时候,遇到了添加自定义方法,项目启动报错原因,现总结如下:
首先定义实体类
@Entity @Table(name = "user") Class User{ @Id @GeneratedValue int id; @Column String age; @Column String school; @Column String userName; set,get方法 (省略) } public interface UserRepository extends JpaRepository<User, Long> { List<User> findByUsernameLike(String username); List<User> aaa(); }
启动项目时,项目报错提示信息为:
org.springframework.data.mapping.PropertyReferenceException: No property aaa found for type com.fpi.safety.common.entity.po.User
再将List<User> aaa();方法去掉后,项目又可以正常启动运行
是什么原因呢?
经查找,原来是继承jpa,必须满足一些规则,规则如下
Spring Data JPA框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如find,findBy,read,readBy,get,getBy,然后对剩下的部分进行解析。
假如创建如下的查询:findByUserName(),框架在解析该方法时,首先剔除findBy,然后对剩下的属性进行解析,假设查询实体为User
1:先判断userName(根据POJO规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;
2:从右往左截取第一个大写字母开头的字符串此处是Name),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设用户为查询实体的一个属性;
3:接着处理剩下部分(UserName),先判断用户所对应的类型是否有userName属性,如果有,则表示该方法最终是根据“User.userName”的取值进行查询;否则继续按照步骤2的规则从右往左截取,最终表示根据“User.userName”的值进行查询。
4:可能会存在一种特殊情况,比如User包含一个的属性,也有一个userNameChange属性,此时会存在混合。可以明确在属性之间加上“_”以显式表达意思,比如“findByUser_NameChange )“或者”findByUserName_Change()“
从上面,我们可以得知,jap在解析是,aaa在user类中是没有属性的,所以报错No property aaa found.
如果我们想要使用jap框架,又不想再多增加一个自定义类,则必须符合其命名规则
如果,你记不住jpa的规则也没关系,你可以自己再多写一个类来实现自定义查询方法
如下:
1. 自定义一个接口,该接口用来声明自己额外定义的查询。
public interface UseerRepositoryTwo { public List<User> searchUser(String name, int id); }
2. 创建一个接口,该接口 extends JpaRepository 或者 CurdRepository, 以及上面自己定义的接口 UseerRepositoryTwo
public interface UserRepositoryTwoService extends CrudRepository<LogDTO, Integer>, CustomizedLogRepository { }
3. 实现UserRepositoryTwoService
注意此处的类名,必须以 2 中创建的接口的名字UserRepositoryTwoService,后面加上 Impl 来声明,而不是写成 UseerRepositoryTwoImpl
public class UserRepositoryTwoServiceImpl implements UserRepositoryTwoService { @Autowired @PersistenceContext private EntityManager entityManager; @Override public List<User> searchLogs(int Id, String name) { ...... } }
自己在写自定义实现即可
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。