一、搭建环境
1、导入jar包 2、导入配置文件(applicationContext.xml以及struts2.xml以及jdbc.properties和日志信息) 3、在web.xml中指定加载配置文件(监听器以及拦截器)
注解加配置文件进行开发
二、项目开始进行
创建实体类:要注意下面这些注解,这是最基本注解
@Entity
@Table(name="sys_user")
public class User {
@Id
@Column(name="user_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
@Column(name="user_code")
private String userCode;
@Column(name="user_name")
private String userName;
@Column(name="user_password")
private String userPassword;
@Column(name="user_state")
private String userState;
//生成get,set方法
}
创建UserAction:首先继承ActionSupport类然后实现ModelDriven接口,记得加泛型
//类上加上以下注解
@Controller
@Scope("prototype")
@ParentPackage("strutd-default")
@Namespace("/")
@Results({
@Result(location="/jsp/success.jsp",name="success",type="redirect")
})
写一个方法,当用户注册发送请求时,用来保存用户。需要在该方法上加@Action
注解
UserAction类:其中注入了UserService接口
@Controller
@Scope("prototype")
@ParentPackage("struts-default")
@Namespace("/")
@Results({
@Result(location="/jsp/success.jsp",name="success",type="redirect")
})
public class UserAction extends ActionSupport implements ModelDriven {
@Autowired
private UserService userService;
@Action("userAction_save")
public String save(){
//1.获得数据----模型驱动自动封装数据
//2.调用service保存数据
userService.save(user);
return "success";
}
private User user = new User();
@Override
public Object getModel() {
return user;
}
}
创建UserService接口(不用加注解),创建UserServiceImpl实现类(要加注解@Service
以及开启事务注解@Transactional
)
注入UserDao
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void save(User user) {
userDao.save(user);
}
}
创建UserDao接口,创建UserDaoImpl实现类,记得加注解@Repository
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private HibernateTemplate hibernateTemplate;
@Override
public void save(User user) {
hibernateTemplate.save(user);
}
}
启动:有错误:There is no Action mapped for namespace [/] and action name [userAction_save发现把Action的save方法设置为private了,真是让人头疼,一定是自己手抖了
使用find()方法的时候,出现空指针异常,我都没有传参,竟然报空指针异常,原来是在Action层中没有注入Service,我真的好气,找了半天的错误
用户名唯一性校验
这里是在用户名的输入框添加onblur事件,要发的是ajax请求,但是我想写的是使用$.post()
的方式,结果实际写成了$.ajax()
,以至于使用属性封装没有获取到参数值。
在dao层,因为虽然只有一个值,但是find方法返回的仍是一个list,所以需要把count(*)的值获取出来,并转成int格式
return Integer.valueOf(count.get(0).toString());
在执行注册提交的时候,需要在form标签里进行判断能否进行注册,onsubmit=“return checkSubmit()”
再在表单任意位置写一个input隐藏域,其中根据其value值来进行判断提交还是不提交,
这里我把input的id值和提交方法的方法名写为了同一个名字,结果导致方法没有执行。
用户退出模块
友好显示,询问是否退出,添加onclick事件,但是因为使用的是a标签,所以要让a标签的href属性失效,所以这时需要href="javascript:void(0)"
,即阻止后续运行。
js访问服务器,使用location.href=""
Action 中将session清除,返回到login.jsp页面,因为index页面使用的是框架集,会导致只有顶部进行跳转,下面还是index的页面,此时需要在login.jsp页面进行添加如下js代码。
<script type="text/javascript">
window.onload=function(){
if(top.location!=self.location){//判断地址栏的地址和自己的地址是否一致
//不一致,将自己的地址赋给地址栏地址,这样就直接跳到了login.jsp页面
top.location=self.location;
}
}
</script>
修改密码模块
用户名框不能够允许被修改,所以input框加上属性readonly="readonly"
这里使用的是hibernate,直接使用update方法,所以传入的是修改后的User对象,此时user从session中获取,密码从模型驱动中获取,然后set password进入从session中获取的user对象里,
字典表
整合表又称为字典表,字典表中的数据就是共有数据。
创建客户表,以及字典表,客户表中的某些字段需要引用字典表
//特殊字段
//@JoinColumn(name="数据库中新增列的列名",referencedColumnName="指向主表的主键,即外键指定的那个表就是主表")
@JoinColumn(name="cust_source",referencedColumnName="dict_id")
@ManyToOne(targetEntity=BaseDict.class)
private BaseDict baseDictSource;
@JoinColumn(name="cust_industry",referencedColumnName="dict_id")
@ManyToOne(targetEntity=BaseDict.class)
private BaseDict baseDictIndustry;
@JoinColumn(name="cust_level",referencedColumnName="dict_id")
@ManyToOne(targetEntity=BaseDict.class)
private BaseDict baseDictLevel;
baseDictLevelList = baseDictService.findBaseDictByTypeCode("006");
baseDictSourceList = baseDictService.findBaseDictByTypeCode("009");
baseDictIndustryList = baseDictService.findBaseDictByTypeCode("001");
查询到这些list以后,存放到值栈中,可以手动或者自动,自动:即是使用属性封装,然后在页面使用El表达式进行获取,
在此又犯了一个错误:在前端页面没有获取到值,这些list是空的,后来发现在配置result的时候,使用了重定向,而重定向是不能够共享数据的
另外:在使用模型驱动的时候,在把当前类添加到Action中,要节点new出来对象。
删除客户模块
根据id进行删除,因为这些都是新增的,所以之前的项目使用的是使用on绑定事件,现在可以使用js并且里面传参进行。传参的时候,里面的参数要加上单引号,这样当参数是字符串和数字时都支持。
function deleteCust(custId){
if(confirm("确认删除吗?")){
location.href="${pageContext.request.contextPath }/customerAction_delete?custId="+custId;
}
}
这里传的是custId,在Action中根据customer删除,此时的customer只有custId的值
@Action("customerAction_delete")
public String delete(){
customerService.delete(customer);
return "findAll";
}
修改模块出现该异常: java.lang.IllegalArgumentException: id to load is required for loading
原因是在dao 层,使用get方法,其没有获取到id。
前端传id的时候出错
<a href="${pageContext.request.contextPath }/customerAction_editUI?custId=${tempCustomer.custId}">修改</a>
修改的时候,让下拉框的值与要修改的对象查出的值是相同的,使用js进行控制。
$(function(){
$("#custLevel option[value='${tempCustomer.baseDictLevel.dictId }']").prop("selected",true);
$("#custSource option[value='${tempCustomer.baseDictSource.dictId }']").prop("selected",true);
$("#custIndustry option[value='${tempCustomer.baseDictIndustry.dictId }']").prop("selected",true);
})
但是在条件查询的时候,进行回显数据时,因为数据使用的是模型驱动进行封装,所以,直接使用Customer的属性名即可,如果属性名是对象,需要继续用里面的数据,继续点(.)就可以了
多对一以及一对多
一对多,一般会在一方放置多方的实体集合对象,
多对一,一般会在对方放置一方的实体对象。
linkman
//描述多对一
@ManyToOne(targetEntity=Customer.class)
@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
private Customer customer;
在表单进行提交的时候,name属性提交的是customer.custId
对象找到外键,然后根据外键把值给该列
小技巧
在使用js时,可以先使用弹出框,看当前js是否可用,以防止后面出现js没有效果,难以找到问题
前端页面不显示查到的信息
因为使用了模型驱动,所以以为可以直接返回book,前端进行获取就可以了,发现如果查询条件里没有使用book,是可以在前端页面获取的到,但是查询的时候使用了book,返回的时候就需要使用别的名字才可以在前端获取的到
book= bookService.findBookById(book);
//更改后:
Book tempbook = bookService.findBookById(book);
ActionContext.getContext().getValueStack().set("tempbook", tempbook);
直接删除客户的时候,要级联删除联系人
需要在一对多的一方配置cascade=CascadeType.REMOVE,这个时候进行删除的时候需要先获取到要删除的客户,然后再进行将客户删除,此时可以级联删除下面的联系人。
使用下面的方式也能将该单选框选中
<input type="radio" value="1" ${tempLinkman.lkmGender=="1" ? "checked" : "" } name="lkmGender" >男
设置分页时注意问题
需要将查询总记录数放在前面,将查询分页数据放在后面,理由:如果查询分页数据,会将之前传入到限定参数自动封装进去,并且拆分不出来。而先查询总记录数时,使用投影查询,对于投影查询是可以自己设置的,涉及到分页查询数据时,只需要将投影查询设置为null即可,会自动覆盖之前的条件。
PageBean<Linkman> pageBean = new PageBean<>(pageNum,pageSize);
//查询总记录数
//使用投影查询
detachedCriteria.setProjection(Projections.rowCount());
int totalRecord = linkmanDao.findRecord(detachedCriteria);
//查询记录数据
//投影查询的条件可以清除掉,但是对于传入的分页限制参数不能分离,所以把查询总记录数放在上面
detachedCriteria.setProjection(null);
List<Linkman> lkmList = linkmanDao.findData(detachedCriteria,pageBean.getStartIndex(),pageSize);
pageBean.setTotalRecord(totalRecord);
pageBean.setData(lkmList);
return pageBean;
这里有一个问题,之前写的是在下一页或者上一页的时候,直接请求指定的Action,但是这样如果是条件查询得到的结果,会把查询条件丢失,所以此时需要点击上一页或者下一页的时候,把查询条件的表单进行提交,此时每次查询都是根据条件来进行的。
这里存在一个问题,就是因为是提交表单,通过隐藏域的方式将当前页的值进行提交过去,此时如果点击到第二页,再进行条件查询的时候,会把当前页是第二页这个信息提交过去,所以在页面加载的时候,把这个隐藏域的value值清空。
$("#pageNumber").val("");
当进行下一页的时候,出现HTTP Status 404 - No result defined for action cn.itcast.ssh.action.BookAction and result input
原来是因为提交的是查询的表单,而在select中的option中,有一个“请选择的选项”,但是我没有写value属性,所以报了这个错误,此时应该写上value属性,让其值为空
<select name="category.categoryId" style="width:20">
<option value="">--请选择--</option>
<c:forEach var="tempCategory" items="${categoryList }">
<option ${category.categoryId==tempCategory.categoryId?"selected":"" } value="${tempCategory.categoryId }">${tempCategory.categoryName }</option>
</c:forEach>
</select>
在使用JSONArray进行字符串转换的时候,报没有session的错误
这是因为hibernate的懒加载问题,当根据客户的id去查找其下面的联系人的时候,没有马上查询联系人表中的customer,事务在Service层响应回来结束,在Action层进行Json转换的时候,需要将linkman表中的所有数据都转换为json,此时hibernate会再次去访问数据库查询customer属性的具体信息,但是此时session已经关闭了。
当选中客户时,自动展现对应的联系人,此时使用Ajax,在Action中将json数据响应回去的时候,因为是自己手动响应回去,此时会出现乱码问题,所以要手动解决乱码问题。
@Action("VisitAction_showLkm")
public String showLkm() throws IOException{
List<Linkman> linkmans = linkmanService.findBycustId(custId);
JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes(new String[]{"customer","visits"});
String json = JSONArray.fromObject(linkmans, jsonConfig).toString();
ServletActionContext.getResponse().setHeader("content-type", "text/html;charset=utf-8");
ServletActionContext.getResponse().getWriter().print(json);
return NONE;
}
function selectLkm(custIdEle){
$("#linkmaninfo").html(" ");
$.post("${pageContext.request.contextPath}/VisitAction_showLkm",{"custId":custIdEle.value},function(data){
$(data).each(function(){
//函数中的每一个this都代表一个linkman对象
$("#linkmaninfo").append("<option value='"+this.lkmId+"'>"+this.lkmName+"</option>");
});
},"json");
}
使用js进行 添加信息的时候,一定要记得把之前的信息清空。
属性驱动的时候,需要根据需要添加set,get方法,set方法是将页面传递的值设置进来,get方法是为了回显数据
textare文本框没有value属性,所以设置值的时候就和普通的div一样,直接在标签的中间写就可以了。
<textarea name="visitDetail" rows="5" cols="25">${currVisit.visitDetail }</textarea>
权限设置
首先在xml中进行配置,并且创建自己自定义的拦截器
public class AuthInterceptor extends MethodFilterInterceptor {
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
//如果session中有user对象,就放行,没有就拦截。
User user = (User) ServletActionContext.getRequest().getSession().getAttribute("user");
if(user!=null){
invocation.invoke();
}
return "loginJsp";
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
//在Action中要使用以下的包名
<package name="crm" extends="struts-default" namespace="/">
<interceptors>
<!-- 自定义一个拦截器 -->
<interceptor name="myInterceptor" class="cn.itcast.web.action.AuthInterceptor"></interceptor>
//这个是自己自定义的拦截器栈,覆盖了之前的,所以里面要手动加上之前的
<interceptor-stack name="myStack">
<interceptor-ref name="myInterceptor">
<param name="excludeMethods">login,save</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"></default-interceptor-ref>
<global-results>
<result name="loginJsp" type="redirect">/login.jsp</result>
</global-results>
</package>
</struts>
这里是配置了自己的拦截器栈,而且包的名字也已经改变,所以在Action中不能够直接使用之前默认的包名了,需要使用现在配置的包名
对于条件判断
int pageSize=2;
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Book.class);
//判断用户名
if(!StringUtils.isBlank(book.getBookName())){
detachedCriteria.add(Restrictions.like("bookName", "%"+book.getBookName()+"%"));
}
if(book.getCategory()!=null && book.getCategory().getCategoryId()!=null){
detachedCriteria.add(Restrictions.eq("category.categoryId", book.getCategory().getCategoryId()));
System.out.println(book.getCategory().getCategoryId());
}
PageBean<Book> pageBean = bookService.findAllBook(detachedCriteria,pageNum,pageSize);