spring SpringMVC JPA(SSJ)项目实战二——查询
SSJ(Spring SpringMVC JPA)项目
智能销售系统
1.1.完成jpa-spec功能的自定义扩展
1.1.扩展原理
- 直接创建BaseRepository来继承JpaRepository接口
增强的抽象功能方法 还有继承的JpaRepository的方法
2.BaseRepositoryImpl类,继承SimpleJpaRepository类,实现BaseRepository
包含了BaseRepository,SimpleJpaRepository功能
3.EmployeeRepository实现BaseRepository,继承
包含了自己的功能,以及SimpleJpaRepository
4.BaseRepositoryImpl去实现EmployeeRepository,继承BaseRepositoryImpl
有了所有方法
BaseRepository接口
直接创建BaseRepository来继承JpaRepository接口
/**
* 自定义一个Repository,它是JpaRepository的功能基础上继承增强
* 在上面添加@NoRepositoryBean标注,这样Spring Data Jpa在启动时就不会去实例化BaseRepository这个接口
*/
@NoRepositoryBean//Spring Data Jpa在启动时就不会去实例化BaseRepository这个接口
public interface BaseRepository <T, ID extends Serializable> extends JpaRepository<T,ID>,JpaSpecificationExecutor<T> {
//根据Query拿到分页对象(分页)
Page findPageByQuery(BaseQuery baseQuery);
//根据Query拿到对应的所有数据(不分页)
List<T> findByQuery(BaseQuery baseQuery);
//根据jpql与对应的参数拿到数据
List findByJpql(String jpql,Object... values);
}
BaseRepositoryImpl功能实现
定义好自定义的方法后,我们现在通过一个基本的Repository类来实现该方法:
首先添加BaseRepositoryImpl类,继承SimpleJpaRepository类,使其拥有Jpa Repository的基本方法。
我们发现Repository有两个构造函数:
lSimpleJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager)
lSimpleJpaRepository(Class domainClass, EntityManager em)
这里我们实现第二个构造函数,拿到domainClass和EntityManager两个对象。
public class BaseRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements BaseRepository<T,ID> {
private final EntityManager entityManager;
//必需要实现父类的这个构造器
public BaseRepositoryImpl(Class<T> domainClass, EntityManager em) {
super(domainClass, em);
this.entityManager = em;
}
@Override
public Page findPageByQuery(BaseQuery baseQuery) {
//第一步:拿到所有高级查询条件
Specification spec = baseQuery.createSpec();
//第二步:拿到排序的值
Sort sort = baseQuery.createSort();
//第三步:根据条件查询分页数据并且返回
Pageable pageable = new PageRequest(baseQuery.getCurrentPageto(), baseQuery.getPageSize(),sort);
Page<T> page =super.findAll(spec, pageable);
return page;
}
@Override
public List<T> findByQuery(BaseQuery baseQuery) {
//第一步:拿到所有高级查询条件
Specification spec = baseQuery.createSpec();
//第二步:拿到排序的值
Sort sort = baseQuery.createSort();
//第三步:拿到数据返回
return findAll(spec, sort);
}
@Override
public List findByJpql(String jpql, Object... values) {
//第一步:创建Query对象
Query query = entityManager.createQuery(jpql);
//第二步:把值设置到Query对象中去
if (values!=null) {
for (int i = 0; i < values.length; i++) {
query.setParameter(i + 1, values[i]);
}
}
//第三步:返回数据
return query.getResultList();
}
}
1.2.创建自定义RepositoryFactoryBean
RepositoryFactoryBean负责返回一个RepositoryFactory,Spring Data Jpa 将使用RepositoryFactory来创建Repository具体实现,这里我们用BaseRepositoryImpl代替SimpleJpaRepository作为Repository接口的实现。这样我们就能够达到为所有Repository添加自定义方法的目的。
做法:
继承JpaRepositoryFactoryBean,复写方法,更改配置
package cn.itsource.aisell.repository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import javax.persistence.EntityManager;
import java.io.Serializable;
public class BaseRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T,S,ID> {
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new MyRepositoryFactory<T,ID>(entityManager); //注:这里创建是我们的自定义类
}
//继承JpaRepositoryFactory后,把返回的对象修改成我们自己的实现
private static class MyRepositoryFactory<T,ID extends Serializable> extends JpaRepositoryFactory {
private final EntityManager entityManager;
/**
* Creates a new {@link JpaRepositoryFactory}.
*
* @param entityManager must not be {@literal null}
*/
public MyRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
//这里返回最后的功能对象
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return new BaseRepositoryImpl<T,ID>((Class<T>)information.getDomainType(),entityManager);
}
//确定功能对象的类型
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return BaseRepositoryImpl.class;
}
}
}
更改配置
<!-- Spring Data Jpa配置 -->
<!-- base-package:扫描的包 -->
<jpa:repositories base-package="cn.itsource.aisell.repository"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"
factory-class="cn.itsource.aisell.repository.BaseRepositoryFactoryBean"/>
1.3.让dao层的类去继承BaseRepository
这里继承接口,是因为我们创建了BaseRepositoryFactoryBean。
功能一:实现对员工的分页查询
-
domain层
- 建立父类表:包含一些所有表都有的东西
以后每一个表都可以继承它
@MappedSuperclass:表明他是一个父类表,以后所有继承他的都可以拿到他的属性
@MappedSuperclass
public class BaseDaomain {
@Id
@GeneratedValue
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
employee表
@Entity
@Table(name = "employee")
public class Employee extends BaseDaomain{
private String username;
private String password;
private String email;
private Integer age;
private String headImage;
public String getUsername() {
return username;
}
public String getHeadImage() {
return headImage;
}
public void setHeadImage(String headImage) {
this.headImage = headImage;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Employee{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", age=" + age +
", headImage='" + headImage + '\'' +
", id=" + id +
"} ";
}
}
pository(dao)
在上面的jpa-spec扩展的基础上
添加一个接口去继承BaseRepository:可以在这里定义一些自己需要的方法
package cn.itsource.aisell.repository;
import cn.itsource.aisell.domain.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface EmployeeRepository extends BaseRepository<Employee,Long>{
//模糊查询
List<Employee> findByUsernameLike(String username);
//查询名字与密码
List<Employee> findByUsernameAndPassword(String username,String Password);
//自己写JPQL查询
@Query("select o from Employee o where o.username=?1")
List<Employee> findByUsername(String username);
//通过顺序确定参数给谁
@Query("select o from Employee o where o.username =?1 and o.password=?2")
Employee login1(String username,String password);
//通过名称确定参数给谁
@Query("select o from Employee o where o.username =:username and o.password=:password")
Employee login2(@Param("username") String username, @Param("password") String password);
//原生sql
@Query(nativeQuery = true,value = "SELECT COUNT(*) FROM employee")
Long getCount();
}
Service层
1.创建BaseService公共接口:提供公共方法
package cn.itsource.aisell.service;
import cn.itsource.aisell.query.BaseQuery;
import org.springframework.data.domain.Page;
import java.io.Serializable;
import java.util.List;
public interface IBaseService<T, ID extends Serializable> {
void save(T t);
void delete(ID id);
T findOne(ID id);
List<T> findAll();
Page findPageByQuery(BaseQuery baseQuery);
List<T> findByQuery(BaseQuery baseQuery);
List findByJpql(String jpql,Object... values);
}
2.创建BaseServiceImpl去实现BaseService
由于查询比较多,所以在类前配置@Transactional(readOnly = true,propagation = Propagation.SUPPORTS),让他不自动添加事务。
然后在要添加事物的方法前加 @Transactional 添加事务
package cn.itsource.aisell.service.impl;
import cn.itsource.aisell.query.BaseQuery;
import cn.itsource.aisell.repository.BaseRepository;
import cn.itsource.aisell.service.IBaseService;
import com.sun.xml.internal.bind.v2.model.core.ID;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.List;
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public abstract class BaseServiceImpl<T,ID extends Serializable> implements IBaseService<T,ID> {
@Autowired
private BaseRepository<T,ID> baseRepository;
@Override
@Transactional
public void save(T t) {
baseRepository.save(t);
}
@Override
@Transactional
public void delete(ID id) {
baseRepository.delete(id);
}
@Override
public T findOne(ID id) {
return baseRepository.findOne(id);
}
@Override
public List<T> findAll() {
return baseRepository.findAll();
}
@Override
public Page findPageByQuery(BaseQuery baseQuery) {
return baseRepository.findPageByQuery(baseQuery);
}
@Override
public List<T> findByQuery(BaseQuery baseQuery) {
return baseRepository.findByQuery(baseQuery);
}
@Override
public List findByJpql(String jpql, Object... values) {
return baseRepository.findByJpql(jpql, values);
}
}
3.创建IEmployeeService继承BaseService
public interface IEmployeeService extends IBaseService<Employee,Long>{
}
4.创建IEmployeeServiceImpl继承BaseServiceimpl实现IEmployeeService
添加Service:不能继承
@Service//service不能继承的
public class EmployeeServiceImpl
extends BaseServiceImpl<Employee,Long> implements IEmployeeService {
}
Controller
1. 不管三七二十一:先搞一个父接口
public interface IBaseController {
}
2.来一个EmployeeController
package cn.itsource.aisell.web.controller;
import cn.itsource.aisell.common.UiPage;
import cn.itsource.aisell.domain.Employee;
import cn.itsource.aisell.query.EmployeeQuery;
import cn.itsource.aisell.service.IEmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller
@RequestMapping("/employee")
public class EmployeeController implements IBaseController {
@Autowired
private IEmployeeService iEmployeeService;
@RequestMapping("/index")
public String index(){
return "employee/employee";
}
@RequestMapping("/page")
@ResponseBody
public UiPage getlist(EmployeeQuery query){
System.out.println(query);
return new UiPage(iEmployeeService.findPageByQuery(query));
}
}
Query(接受所有前台数据)
从前台会传回很多数据,我们需要去接受这些数据,一个一个 接收比较麻烦,我们定义一个类来装这些数据。
- 都会传回那些数据
(1)当前页
(2)每页的数据
(3)排序名称
(4)排序类型
(5)表里面的数据
- 一个表就有一个类,而每一个类的数据都有很多的相同的,我们就可使用一个公共的借口,来装这些从前端传回来的公共数据。
//当前页
private int currentPage = 1;
//每页条数
private int pageSize = 10;
//排序的名称
private String orderName;
//排序的类型 DESC/ASC
private String orderType = "ASC";
3.前段页面的起始夜是1,而我们的起始也是0,所以我们需要改变一下:记住不要修改原代码,防止以后使用出错(我们以后就是需要一个当前页,灭有去减去1的数据,就会出错)
//前段页面的起始夜是1,而我们的起始也是0
public int getCurrentPageto() {
return currentPage-1;
}
4,我们的查询会经常用到排序,所以我们要抽取排序方法到BaseQuery(父类)。
//抽取排序方法
public Sort createSort(){
//创建排序对象
Sort sort = null;
//只有排序条件不会空,我们才创建Sort这个对象
if(StringUtils.isNotBlank(orderName)){
//默认类型
Sort.Direction sortType = Sort.Direction.ASC;
//toUpperCase() 防止输入小写
if(orderType.toUpperCase().equals("DESC")){
sortType = Sort.Direction.DESC;
}
sort = new Sort(sortType,orderName);
}
return sort;
}
5.每一表都有一些不同的数据,那么一个表就对应一个类
同时,我们的查询条件也要抽取:
public class EmployeeQuery extends BaseQuery{
@Override
public Specification createSpec() {//父类是有一个这个的抽象方法,子类这是实现
Specification<Employee> spec = Specifications.<Employee>and()
.like(StringUtils.isNotBlank(username), "username", "%" + username + "%")
.like(StringUtils.isNotBlank(email), "email", "%" + email + "%")
.lt(age != null, "age", age)
.build();
return spec;
}
private String username;
private String email;
private Integer age;
Common
这是一个公共包:
- 解决前台页面传回的分页总数与分页数据名字与后台不一致的问题
(1)创建一个类UiPage
package cn.itsource.aisell.common;
import org.springframework.data.domain.Page;
import java.util.List;
public class UiPage {
private List rows;
private Long total;
public UiPage(Page page){
this.rows=page.getContent();
this.total=page.getTotalElements();
}
public List getRows() {
return rows;
}
public void setRows(List rows) {
this.rows = rows;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
}
controller接受数据
@RequestMapping("/index")
public String index(){
return "employee/employee";
}
@RequestMapping("/page")
@ResponseBody
public UiPage getlist(EmployeeQuery query){
System.out.println(query);
return new UiPage(iEmployeeService.findPageByQuery(query));
}
返回一个UiPage对象回去
功能二:用easyUI做一个主页面
1.页面布局 layout
Lalyout :依懒panel,resizable。
布局容器有5个区域:北、南、东、西和中间。中间区域面板是必须的,边缘的面板都是可选的。每个边缘区域面板都可以通过拖拽其边框改变大小,也可以点击折叠按钮将面板折叠起来。布局可以进行嵌套,用户可以通过组合布局构建复杂的布局结构。
<div id="cc" class="easyui-layout" style="width:600px;height:400px;">
- <div data-options="region:'north',title:'North Title',split:true" style="height:100px;"></div>
- <div data-options="region:'south',title:'South Title',split:true" style="height:100px;"></div>
- <div data-options="region:'east',iconCls:'icon-reload',title:'East',split:true" style="width:100px;"></div>
- <div data-options="region:'west',title:'West',split:true" style="width:100px;"></div>
- <div data-options="region:'center',title:'center title'" style="padding:5px;background:#eee;"></div>
- </div>
如果要填满整个网页:直接在body中,添加layout
<body class="easyui-layout"></body>
split:true:可拖动边框。
我们在这里只是需要三个:
<body class="easyui-layout">
<div data-options="region:'north'" style="height:100px;background-image:url(/img/back2.jpg);" >
<div style="width: 800px;">
<marquee direction="right" behavior="alternate" scrollamount="15" scrolldelay="10" height="100" align="absmiddle">
<font face="隶书" size="9"> 源码时代智能销售系统</font>
</marquee>
</div>
</div>
<div data-options="region:'west',title:'菜单',split:true" style="width:200px;background-image:url(/img/back2.jpg);">
<ul id="tree"></ul>
</div>
<div id="table" class="easyui-tabs" data-options="region:'center'" >
<div title="主页面">
<p>我是主页面</p>
</div>
</div>
</body>
2.实现tree菜单
<ul id="menutree"></ul>
$("#menutree").tree({//添加菜单
method:"get",
url:'json/menuTree.json',
)}
3.点击菜单栏的菜单在中间添加一个panel
onClick:function(node){
//获取节点的名称,以及属性
var $text=node.text;
var $iconCls = node.iconCls;
$("#main").tabs('add',{//添加页面
title: $text,
iconCls:$iconCls,
content: '<div style="padding:10px">'+$text+'</div>',
href:'employee.html',
closable: true
})
}
4.点击主菜单不会显示
If(node.url):子菜单有url,主菜单没有
5.点击已经存在在主页面显示的菜单,不会再显示
//判断是否已经已经点击了
var tabspanel=$("#main").tabs("getTab",$text);
if(tabspanel){
//选中相应的面板
$("#mainUI").tabs("select",$text)}
<script>
$(function () {
$('#tree').tree({
url:'/json/menu.json',
onClick: function(node){
//判断是否为子菜单
if(node.url){
var nodeName = node.text;
var tab = $("#table").tabs("getTab",nodeName);
//判断这个选项卡是否已经存在,存在就选中,不存在重新打开
if(tab){
$("#table").tabs("select",nodeName)
}else {
//嵌入一个单独的页面
var connect= '<iframe scrolling="auto" frameborder="0" src="'+node.url+'" style="width:100%;height:100%;"></iframe>';
//添加选项卡
$('#table').tabs('add',{
title:nodeName,
content:connect,
closable:true
});
}
}
}
});
})
</script>
功能三:实现用户管理界面
1.添加一个dataGrid显示所有数据
<table id="dataGrid" class="easyui-datagrid"
data-options="
url:'/employee/page',
fitColumns:true,
singleSelect:true,
fit:true,
toolbar:'#gao',
pagination:true">
<thead>
<tr>
<th data-options="field:'headImage',width:100,formatter:gethead" >头像</th>
<th data-options="field:'username',width:100">用户名</th>
<th data-options="field:'password',width:100">密码</th>
<th data-options="field:'email',width:100,align:'right'">邮箱</th>
<th data-options="field:'age',width:100,align:'right'">年龄</th>
<th data-options="field:'department',width:100,align:'right',formatter:getdepartment">部门</th>
</tr>
</thead>
</table>
1.显示头像
function gethead(value,row,index) {
return "<img width='60px' src='"+value+"' alt='没有头像'>"
}
2.部门下拉(下面有后台部门的代码)
部门:<input name="departmentId" class="easyui-combobox"
panelHeight="auto"
data-options="valueField:'id',textField:'name',url:'/util/dpartment'" />
function getdepartment(value,row,index) {
return value?value.name:"无部门"
}
3.高级查询
$(function () {
var datagrid = $('#dataGrid');
$("*[data-method]").on("click",function () {
var method=$(this).data("method");//拿到方法名字
itsource[method]();//根据方法名执行相应 的方法
})
window.itsource={
search:function () {
//获取from表单的值
var par=$("#searchform").serializeObject();
//刷新页面
datagrid.datagrid("load",par);
}
}
})
功能四:添加部门
Domain
@Entity
@Table(name = "department")
public class Dpartment extends BaseDaomain {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dpartment{" +
"name='" + name + '\'' +
", id=" + id +
"} ";
}
}
与employee建立多对一的关联(在employee中)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="department_id")
//Json Ignore(忽略) Properties(属性)
//@JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"})
private Dpartment department;
当设置了懒加载的时候会报两个错:
- noSession:
原因:当我们在连表的时候配置了懒加载的时候,那么我们的在查询后就将session给关了
我们的department是懒加载,是需要的时候才去查询,但是我们的session已经关了,所以就会报一个nosession
解决:在web.xml中加上
<!-- 加上OpenEntityManager -->
<filter>
<filter-name>openEntity</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntity</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2.No serializer:这个问题在使用Springmvc+jpa中加懒加载一定会出现这个问题。
原因:因为SpringMVC会返回一些json数据,这些数据中有一些序列化要求。当没有懒加载对象时是没有问题的,现在使用jpa返回懒加载对象,它自动在里面加了一些属性来完成懒加载功能("hibernateLazyInitializer","handler","fieldHandler")。
解决办法一:麻烦,每一个关联都要写
在类上加属性:生成的时候把这个字段忽略了:
@JsonIgnoreProperties(value={"hibernateLazyInitializer","handler","fieldHandler"})
解决办法二:一次配置
创建一个新的类(重写com.fasterxml.jackson.databind.ObjectMapper)
package cn.itsource.aisell.common;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class CustomMapper extends ObjectMapper {
public CustomMapper() {
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 设置 SerializationFeature.FAIL_ON_EMPTY_BEANS 为 false
this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
}
在applicationContext-mvc.xml中配置
<!-- No serializer:配置 objectMapper 为我们自定义扩展后的 CustomMapper,解决了返回对象有关系对象的报错问题 -->
<property name="objectMapper">
<bean class="cn.itsource.aisell.common.CustomMapper"></bean>
</property>
2.Pository
package cn.itsource.aisell.repository;
import cn.itsource.aisell.domain.Dpartment;
public interface DepartmentRepository extends BaseRepository<Dpartment,Long> {
}
3.Service
- 父类接口
- 子类实现
package cn.itsource.aisell.service;
import cn.itsource.aisell.domain.Employee;
import cn.itsource.aisell.query.EmployeeQuery;
import cn.itsource.aisell.service.IBaseService;
public interface IEmployeeService extends IBaseService<Employee,Long>{
}
import cn.itsource.aisell.domain.Dpartment;
import cn.itsource.aisell.domain.Employee;
import cn.itsource.aisell.service.IDartmentService;
import cn.itsource.aisell.service.IEmployeeService;
import org.springframework.stereotype.Service;
@Service//service不能继承的
public class DpartmentServiceImpl
extends BaseServiceImpl<Dpartment,Long> implements IDartmentService {
}
扩展功能一:排序
添加属性:sortable=true;
添加了这个属性,每一次一点击在前台就会传回sort,order
后台拿到这个数据给orderName orderType
//排序(前台传回来的是sort,order)
public void setSort(String sort){
this.orderName=sort;
}
public void setOrder(String order){
this.orderType=order;
}
由于我们已经写好了排序,这里个这两个自赋值,就可以直接使用了。
隐藏字段
1.到http://www.easyui-extlib.com/的grid部分找到
2.找到需要的js,css
3.查看元素,找到我们需要的js,css
4.拷贝js,css
5.将拷贝的放到项目中去
6.引入
<script src="/js/model/employee.js"></script><%--外部映入要写全--%>
<script type="text/javascript" src="/easyui/plugin/datagrid/jeasyui.extensions.datagrid.getColumnInfo.js"></script>
<script type="text/javascript" src="/easyui/plugin/datagrid/jeasyui.extensions.menu.js"></script>
<script type="text/javascript" src="/easyui/plugin/datagrid/jeasyui.extensions.datagrid.css"></script>
<script type="text/javascript" src="/easyui/plugin/datagrid/jeasyui.extensions.datagrid.columnToggle.js"></script>
7.根据扩展属性配置属性