一:字典表
字典信息:在项目中可能会使用到,已经存在的一些信息。
例如,客户的级别:普通用户,vip用户...
客户的来源:网络营销,电话营销...
客户所属行业:电子商务,房地产...
客户的性别:男,女
在保存用户的时候,这些信息都是已经存在的,不应该让用户让用户来任意填写,
而是通过下拉列表来让用户选择。
这些已经存在的信息称之为字典信息,将字典信息保存在字典表中。
二:表的设计
客户表和级别表,来源表和所属行业表的关系
客户和级别表,行业表,来源表都属于多对一的关系
为了简化开发,可以将三张字典数据合成一张字典表
字典表中的内容
三:实体之间的设计
customer表中的cust_level,cust_source,cust_industry字段属于外键
对应着字典表basedict中的dict_id主键
在多方(客户实体)中存在一方对象(字典实体)的引用
Customer实体设计
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long cust_id;
private String cust_name;
private String cust_phone;
private String cust_mobile;
//配置主外键关系 referencedColumnName外键所指向的主键 name:外键名称
@ManyToOne(targetEntity=BaseDict.class)
@JoinColumn(name="cust_level",referencedColumnName="dict_id")
private BaseDict level; 客户级别
@ManyToOne(targetEntity=BaseDict.class)
@JoinColumn(name="cust_source",referencedColumnName="dict_id")
private BaseDict source; 客户来源
@ManyToOne(targetEntity=BaseDict.class)
@JoinColumn(name="cust_industry",referencedColumnName="dict_id")
private BaseDict industry; 客户所属行业
字典实体(BaseDict)
@Entity
@Table(name="base_dict")
public class BaseDict implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long dict_id; 字典表id
private String dict_type_code; 类别(该条记录是来源,行业还是级别的标识)
private String dict_type_name; 类别的名称(客户来源,客户行业,客户级别)
private String dict_item_name; 来源,级别,行业中的具体项(vip,房地产,网络营销)
private String dict_item_code;
private Integer dict_sort; 排序使用
private Character dict_enable;
private String dict_memo;
知识回顾:Hibernate中查询的api
①:oid 通过id查询 get load方法
②hql:在HQL语句中不可能出现于数据库相关的信息,因为它是面向对象来操作的,
只会出现实体类中的属性或对象如果出现了表名或者表中的字段,
那么肯定是你的表名和类名相同,表中的字段和类中的属性相同
查询的api
Query query = session.createQuery(hql语句);
query.list()查询所有
query.uniqueResult()返回单一值
基本查询:from 类名 查询该实体类映射的表中所有的记录封装为List<Object>
条件查询:
from 类名 where 属性名 like ? 占位符
from 类名 where 属性名 = ?
from 类名 where 属性名 like :name; 名称占位符
设置查询条件:
query.setParameter(0, "%o%");//设置?占位符的参数值
query.setParameter(name, "%o%");//设置命名占位符的参数值
分页查询
String sql = "from Customer";
Query query = session.createQuery(sql);
query.setFirstResult(0);//相当于设置limit的第一个参数,起始索引
query.setMaxResults(3);//相当于设置limit的第二个参数,一页显示多少条记录
hql查询续:
排序
String sql = "from Customer order by 属性名 desc";
//desc降序 默认为升序
聚合函数 count|sum|max|min|avg
select count(cust_id) from Customer
count(*)也ok
投影查询 查询部分列
要求实体类必须要有select后面的构造函数
String hql = "select new Customer(c.cust_id,c.cust_name) from Customer c";
③:qbc查询
session.createCriteria(持久化类的字节码对象)
分页:
setFirstResult(int 开启的索引)
setMaxResults(int 每页显示的条数)
排序:
addOrder(Order.desc|asc(属性名));
统计:
setProjection(Projections.count|sum|min|max|avg(属性名))
setProjection(Projections.rowCount())
条件:
add(Resitrctions.eq|like|gt|lt(属性名称,参数..));
离线查询对象:
DetachedCriteria:api和Criteria中完全相同
DetachedCriteria dc = DetachedCriteria(持久化类的字节码对象);
在web层进行使用,在条件查询的时候对查询的条件进行封装,在调用service层
方法得时候,只需要传递dc对象就ok。
HibernateTemplate(Hibernate模板操作数据库)
save(obj)
update(obj)
delete(obj)
get(class,id)
load(class,id)
findByCriteria(DetachedCriteria dc):qbc查询
findByCriteria(DetachedCriteria dc,int firstResult,int maxResults):qbc分页
find(String hql,Object... args):hql
需求一:添加客户到数据库中
添加客户的页面:
在添加用户的时候,要先从数据库中查询出客户级别,信息来源,所属行业
这些字典信息来让用户进行选择
由两种方式:
①:同步方式 先跳转到action,将查询到的字典内容放置到值栈中,然后再请发到add.jsp
②:异步方式 直接到add.jsp页面,当页面加载完成的时候发送ajax请求
先使用第一种方式:
//查询字典数据
public void getDictValue(){
通过类别可以查询到来源,级别,所属行业下的所有数据
levelList = customerService.findBaseDictByTypeCode("006");
sourceList = customerService.findBaseDictByTypeCode("002");
industryList = customerService.findBaseDictByTypeCode("001");
}
字典数据的集合设置为action的成员属性,提供get方法就可以在jsp页面中获取
add.jsp页面
使用ognl+struts2标签获取致函中的数据(获取客户来源,所属行业类似)
<td>客户级别 :</td>
<td>
<select name="level.dict_id" style="WIDTH: 180px">
<s:iterator value="levelList" var="basedict">
<option value="<s:property value="#basedict.dict_id"/>"><s:property value="#basedict.dict_item_name"/></option>
</s:iterator>
</select>
</td>
customer表中的cust_level,cust_source,cust_industry字段属于外键
对应着字典表basedict中的dict_id主键
表单中
级别的name属性为:level.dict_id
来源的name属性为: source.dict_id
会使用属性封装的方式封装到BaseDict level中的dict_id
在保存Customer的时候会将对应的外键(cust_level,cust_source,cust_industry)保存
需求2:查询客户列表信息(分页+条件查询)
分页使用的ObjectVlaue(PageBean)部分代码
mysql分页 limit ?,?
后台需要的条件:起始索引,一页显示的记录数
前台需要的数据:当前页显示的数据,总页数,总的记录数
前台需要传递的数据:当前页(没有传递默认为1)一页显示的记录数(没有传递,给出默认值)
起始索引:(当前页-1)*一页显示的记录数
总记录数:从数据库中查询而来
总页数:Math.ceil(1.0*总记录数/总页数)
当前页显示的数据:数据库中查询
public class PageBean<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer startIndex = 0; //起始索引
private Integer pageSize = 3; //一页显示的记录数
private Integer pageNumber = 1; //当前页,由前端用户传递,如果用户没有传递默认显示第一页的数据
private List<T> result; //封装查询出来的某一页的数据
private Long totalRecord; //总记录数 从数据库中查询而来
//计算而来 总记录数%一页显示的记录==0?总记录数/一页显示的记录:总记录数/一页显示的记录+1
private Integer totalPage; //总页数
public Integer getTotalPage() {
totalPage = (int) Math.ceil(1.0*totalRecord/pageSize);
return totalPage;
}
//外界来获取起始索引 在内部进行计算
public Integer getStartIndex() {
startIndex = (pageNumber - 1) * pageSize;
return startIndex;
}
public void setPageSize(Integer pageSize) {
if(pageSize != null){
如果前台没有传递,使用默认值
this.pageSize = pageSize;
}else{
this.pageSize = 3;
}
}
public void setPageNumber(Integer pageNumber) {
if(pageNumber != null){
this.pageNumber = pageNumber;
}else{
如果前台没有传递,使用默认值
this.pageNumber = 1;
}
}
}
条件查询:在web层使用DetachedCriteria来封装查询的条件
web层action中的代码
使用DetachedCriteria来封装查询的条件,使用pageBean来封装当前页和一页显示的数据
调用service层方法的时候传递DetachedCriteria对象和pageBean对象
DetachedCriteria dc = DetachedCriteria.forClass(Customer.class);
/*
* dc.add(Restrictions(propertyName, value))查询提交
propertyName:是属性字段 value是条件对应的值
* 然后再使用对象导航查询去字典表中查询数据
*/
if(customer.getCust_name() != null &&!customer.getCust_name().trim().equals("")){
dc.add(Restrictions.like("cust_name","%"+customer.getCust_name().trim()+"%"));
}
/*
* 对查询的条件进行判断和封装 不需要对对象进行非空判断,
因为使用模型封装实体的时候,实体必须手动创建
* 如果下拉列表没有没有被选择,select传递的value为-1
*/
if(customer.getLevel() != null && customer.getLevel().getDict_id() != -1){
dc.add(Restrictions.eq("level", customer.getLevel()));
}
if(customer.getSource() != null && customer.getSource().getDict_id() != -1){
dc.add(Restrictions.eq("source", customer.getSource()));
}
if(customer.getIndustry() != null && customer.getIndustry().getDict_id() != -1){
dc.add(Restrictions.eq("industry", customer.getIndustry()));
}
//传递的参数为离线查询对象(封装条件)和分页需要的pageBean
pageBean = customerService.findList(dc,pageBean);
service层的代码
public PageBean findList(DetachedCriteria dc,PageBean pageBean) {
/*
* 需要填充的数据为当前页显示的数据
* 总的记录数
*/
//查询总的记录数(需要传递dc离线查询对象,因为不止是分页,是条件+分页)
Long totalRecord = customerDao.searchTotalRecord(dc);
pageBean.setTotalRecord(totalRecord);
//查询当前页显示的记录
List<Customer> result = customerDao.searchCustomerList(dc,pageBean);
pageBean.setResult(result);
return pageBean;
}
dao层的代码:
@Override
public Long searchTotalRecord(DetachedCriteria dc) {
//设置投影(聚合)
dc.setProjection(Projections.rowCount());
/*
* 查询语句类似于 select count(*) from Customer
* 如果有条件的分页就是select count(*) from Customer where ......
*/
List<Long> record = (List<Long>) hibernateTemplate.findByCriteria(dc);
/*
* 查询完成之后需要去除投影,因为查询完成总记录数之后
* 还需要查询当前页显示的数据 使用的是一个离线查询对象
*/
dc.setProjection(null);
return record.get(0);
}
//分页+条件查询 查询当前页显示的数据
@Override
public List<Customer> searchCustomerList(DetachedCriteria dc, PageBean pageBean) {
List<Customer> customerList = (List<Customer>) hibernateTemplate.findByCriteria(dc, pageBean.getStartIndex(), pageBean.getPageSize());
return customerList;
}
要注意的就是在进行总记录数查询的时候dc.setProjection(Projections.rowCount());
当查询完成的时候去去除投影,因为接下来查询当前页显示的记录数使用的也是同一个
离线查询对象。
查询条件的回显
两种方式:
方式一:使用el表达式进行判断
<option value="${basedict.dict_id }" <c:if test='${basedict.dict_id==industry.dict_id}'>selected='selected'</c:if>>${basedict.dict_item_name}</option>
方式二:使用jqery属性选择器
使用jquery的属性选择器来进行判断
当下拉列表中option中的值与之前选择的值相同时,让匹配的option选中
<script type="text/javascript">
$(function(){
//当页面加载完成的时候使用jquery的属性选择器
$("#level option[value='${level.dict_id}']").prop("selected",true);
$("#source option[value='${source.dict_id}']").prop("selected",true);
})
</script>
前台页面:
当点击下一页或者索引页的时候,使用的是超链接。
但是分页+条件查询,查询的条件在表单中
解决:当点击索引页。上一页下一页之后。
为超链接提供点击事件:
将当前点击的页数动态的放置在表单输入项中
然后提交表单,这样会将查询的条件和当前页一起提交到后台页面
当前页的表单
<input id="pageNumberId" type="text" name="pageBean.pageNumber" value="">
<A href="javascript:toPage('${pageBean.pageNumber+1 }')">后一页</A>
function toPage(pageNumber){
//获取到提交当前页的输入框
$("#pageNumberId").val(pageNumber);
//提交表单
$("#customerForm").submit();
}
需求三:修改客户信息:
先查询后修改:
据客户的id先查询客户信息,然后请求转发到edit.jsp页面显示要修改的客户信息
private Customer customer = new Customer();
//使用模型驱动进行表单数据的封装
@Override
public Customer getModel() {
return customer;
}
使用模型驱动封装要修改的的客户id
此时customer中只有客户的id
这时候如果还使用customer来接收的话,请求转发到edit.jsp页面中
通过${成员属性}是获取不到内容的
因为customer引用重新指向了一个对象
要想在edit.jsp页面中获取到查询到的内容
有三种方式:
一:使用customer来接收
使用ActionContext.getContext.getValueStack().push(customer);
向root栈中存放customer指向的新对象 可以使用${成员属性获取到新内容}
二:创建一个新的Customer findCustomer对象来接收,为该customer也提供相应的get方法
在页面就可以使用${findCustomer.成员属性}来获取导内容
三:使用模型封装的customer来接收
在edit.jsp页面就可以使用${model.成员属性}的方法来获取到内容
no-session问题:
could not initialize proxy - no Session
原因:懒加载的问题
使用id获取要修改的对象时,使用load方法
访问 service 访问dao 返回是linkman的代理对象
代理对象返回给web层
页面获取数据 代理对象在使用时才会真正的去查询 但是session已经关闭
no-session的解决方案:
1、立即查询
2、延长session的存活时间 在web层将代理对象的数据查询完毕后在让session关闭
spring提供了一个Filter ----- OpenSessionInView
<!-- openSessionInView -->
<filter>
<filter-name>OpenSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意一:必须让该过滤器在Struts2核心过滤器之前执行
因为当Struts核心过滤器执行完成之后,action的方法已经被执行
在操作数据库的时候session对象还没有创建,还是会有no-session问题
注意二:OpenSessionInViewFilter
hibernate5.support.OpenSessionInViewFilter
必须和你导入Hibernate的版本一致,在这里是Hibernate5版本
否则可能会有异常:
org.hibernate.SessionFactory.openSession()Lorg/hibernate/classic/Session
no-session:懒加载问题
原生hibernate(非jpa形式)
类级别的延迟
load() 直接查询实体对象时是否延迟
配置<class name="全包名" table="表名称" lazy="true/false"> 默认true
关联级别的延迟
关联类的延迟
linkman关联customer
<many-to-one lazy="false/proxy"> ----默认proxy
关联集合的延迟
customer关联linkman
<set lazy="true/false/extra"> ---默认是true
jpa形式的hibernate
一对多 查询一的一方多 多的一方默认延迟加载
查询customer 对应的linkman 延迟加载
多对一 查询多的一方 一的一方默认立即加载
查询linkman 对应的customer的立即加载
删除客户(一方)删除客户的时候要删除客户下对应的联系人,配置级联删除
cascade=CascadeType.REMOVE
一方放弃维护关系,所以不能直接删除,需要先查后删
//在删除一方的时候先查询后删除,因为查询出来的对象和多表的一方存在关系,可以级联删除
customer = hibernateTemplate.get(Customer.class, customer.getCust_id());
hibernateTemplate.delete(customer);
删除多方的时候可以直接删除,单表操作的时候可以直接删除
ajax的递归错误
There is a cycle in the hierarchy
在json格式转换的时候出现死循环
表之间存在关系,Customer和LinkMan表是一对多的关系
在打印customer对象的时候,由于customer中存在LinkMan
会打印LinkMan对象,在打印LinkMan对象中,又存在Customer对象
递归调用,导致内存溢出
需求:
添加联系人的时候要选择所属客户
在add.jsp页面加载完成的时候,发送ajax请求
获取数据库中的所有客户信息
<script type="text/javascript">
//当页面加载完成的时候,获取所有的客户信息,发送ajax异步请求
$(function(){
$.ajax({
url:"${pageContext.request.contextPath}/customer_findCustomerList.action",
type:"POST",
success:function(data){
$(data).each(function(index){
$("#customerId").append("<option value='"+this.cust_id+"'>"+this.cust_name+"</option>");
});
//数据的回显必须放置在回调函数中,当查询到所有的客户的时候才能进行信息的回显
$("#customerId option[value='${customer.cust_id}']").attr("selected",true);
},
dataType:"json"
});
})
</script>
web层的代码:
//去数据库中查找所有的客户信息
@Action("customer_findCustomerList")
public void findCustomerList() throws IOException{
List<Customer> customerList = customerService.findAll();
//发送的是ajax请求 转换为json格式的数据
JsonConfig jsonConfig = new JsonConfig();
设置不参与转换的字段
jsonConfig.setExcludes(new String[]{"linkMen"});
String json = JSONArray.fromObject(customerList,jsonConfig).toString();
//将json格式的数据写会给浏览器,解决中文乱码问题
HttpServletResponse response = ServletActionContext.getResponse();
response.setCharacterEncoding("UTF-8");
response.getWriter().println(json);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。