本文只要记录在开发工程中遇到的问题以及解决方法以及待改进
做项目时候,先将各种包,接口等做好
然后依次配置xml,先从mybatis,spring,struts2
一些还能改进的地方
待改进
- 商品修改时,如果修改了类别,那么新类别数目+1,旧类别数目-1,删除也是
- 购物车添加商品后,显示慢一步
- 商品显示分页
- 当前位置的显示,一直是手机
- 清空购物车
未完成
配置spring的xml文件中的bean
应该全部配置实例的?!!
1 2 3 4 5 6 7 8 9 10 11
| <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.dy.shoppingSite.dao.UserDao" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean> <bean id="userService" class="com.dy.shoppingSite.service.impl.UserServiceImpl"> <property name="userDao" ref="userMapper"></property> </bean>
|
spring 与 struts2 联合使用加入struts2-spring-plugin
Exception starting filter struts2 Unable to load configuration.
[unknown location] at org.apache.struts2.dispatcher.Dispatcher.init
报错原因是没有struts2-spring-plugin
得到id随机数串
使用UUID.randomUUID()
1 2 3 4
| public static String getID(){ UUID uuid = UUID.randomUUID(); return uuid.toString().replace("-", ""); }
|
ajax和jquery需要服务器端返回一个无参数的函数
使用ajax和jquery来实现,需要服务器端返回一个无参数的函数,参数传递使用response
1 2 3 4 5 6 7 8 9 10 11 12
| public void isExist() { boolean b = userService.isExist(user.getName()); try { PrintWriter writer = ServletActionContext.getResponse().getWriter(); writer.print(b); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } }
|
分页操作
实现搜索条件保存和翻页的结合
在项目中搜索栏中会有各种搜索条件,搜索完成后,需要分页浏览,
但是进入下一页时,要保证搜索条件仍然显示出来
这是需要用form表单将他们放在一起,并用jquery控制分页及整体form提交
page是向后台传送的属性
1 2 3 4 5 6
| <script type="text/javascript"> function goPage(p){ $("#page").val(p); $("#form1").submit(); } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- //为了解决 将搜索内容和page都传到后台--> <form action="goods_listGoodsByPage" method="post" id="form1"'> <!-- 因此添加hidden属性将page和goods一起传到后台,在jquery中goPage实现的 --> <!-- name是传到后台的属性名,id是jquery中使用的属性名 --> <input type="hidden" name="page" id="page" /> <select class="auto" name="goods.categoryId" id="category"> <option value="">选择分类</option> <c:forEach items="${categories}" var="category"> <option value="${category.id}">${category.name}</option> </c:forEach> </select> 商品名: <input class="small" name="goods.name" id="name" type="text" value=""> <button class="btn" type="submit"> <span class="sel">筛 选</span> </button> </form>
|
1 2 3 4 5 6 7
| <div class='pages_bar'> <a href='javascript:goPage(1)' id="first">首页</a> <c:forEach begin="1" end="${pageBean.totalPage}" var="p"> <a href="javascript:goPage('${p}')">${p}</a> </c:forEach> <a href='javascript:goPage(${pageBean.totalPage})' id="last">尾页</a><span>当前第${pageBean.page}页/共${pageBean.totalPage}页</span> </div>
|
先得到旧数据,再更新
在实际中,比如要更新商品,要先得到旧数据,然后对旧数据进行修改,输入到数据库中
这样做的原因是,修改界面不一定所有的属性都要修改,所以直接拿新数据进行修改可能会产生空值
1 2 3 4 5 6 7 8 9 10
| public void updateGoods(Goods goods) { // TODO Auto-generated method stub //这里更新要先将beforeGoods拿来,然后把新的goods中属性付给beforeGoods //因为goods中不一定所有属性都进行更新,会产生空值,导致出错 Goods beforeGoods = goodsDao.getGoodsById(goods.getId()); beforeGoods.setCategory(goods.getCategory()); beforeGoods.setCategoryId(goods.getCategoryId()); goodsDao.updateGoods(beforeGoods); }
|
mybatis相关
传入多个同类型的参数,使用#{序号}取出
1 2 3 4
| <select id="getUserByNameAndPwd" resultType="User"> select * from shop_user where name = #{0} and password = #{1} </select>
|
jdbcType = VARCHAR可以传入空值,且不报错
1 2 3 4 5 6 7
| <insert id="addUser" parameterType="User"> insert into shop_user values(#{id}, #{name}, #{password}, #{phoneNum}, #{money}, #{avatar,jdbcType = VARCHAR}, #{regTime}, #{role} ) </insert>
|
得到多个相同类型的数据
1 2 3 4 5 6
| sets or lists. This problem is known as the "N+1 Selects Problem". In a nutshell, the N+1 selects problem is caused. We can Nest Results for Association --> <select id="getAddress" parameterType="string" resultType="Address"> select * from shop_address where userid = #{0} </select>
|
java可以直接用list取得
1 2 3 4
| public List<Address> getAddress(String userid) { return addressDao.getAddress(userid); }
|
column不一定是数据库中的名字,property是在java中get,set方法的那些属性
association查询一个属性,该属性和另一张表关联,从而查出另一站标的所有属性
1 2 3 4 5 6 7 8 9 10 11
| <!--property是在java中get,set方法的那些属性 --> <resultMap type="Goods" id="GoodsResultMap"> <id column="id" property="id" /> <result column="goodsNo" property="goodsNo" /> <association property="category" javaType="Category"> <id column="id" property="id" /> <!-- 因为goods中也有name属性,因此查询出来的结果会重复。 故把category中的name重新命名为cname,这样就不会重复。 由此可以看出column属性不一定是数据库中的列名, 而是查询后显示的列名 --> <result column="cname" property="name" /> </association> </resultMap>
|
parameterType不是hashmap等时,不需要加所属,反之必加&hashmap注意判空&bind实现模糊查询
parameterType是Goods,是普通的java类型,因此不要goods.name,mybatis直接会用Goods中的get/set方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <select id="getGoodses" resultMap="GoodsResultMap" parameterType="Goods"> <!-- 这里给c.name使用别名cname,因为g中也name列,会重复 --> select g.*, c.name cname from shop_goods g, shop_category c where g.categoryId = c.id <!-- parameterType单一的情况下,就是只有goods或者只有string,(不是hashmap或者list等), 查询时不需要加所属名字的,比如goods.name --> <if test="categoryId != null and categoryId!= '' "> and g.categoryId = #{categoryId} </if> <if test="name != null and name != '' "> <!-- 模糊查询,1.要用bind 将查询的东西与页面的显示联系起来 --> <bind name="name" value="'%' + name + '%'" /> <!-- 模糊查询,2.like关键字模糊查询 --> and g.name like #{name} </if> </select>
|
parameterType是hashmap,含有多种不同类型的值,一定要加上key值,才能取出,比如goods.categoryId,
mybatis直接会用hashmap中的get/set得到key值,然后在用key中的get/set方法得到属性值
hashmap中放多种key时,注意判断key是否为空,否则报错source is null for getProperty(null, "categoryId")
bind实现模糊查询:模糊查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <select id="listGoodsByPage" parameterType="hashmap" resultMap="GoodsResultMap"> select g.*, c.name cname from shop_goods g, shop_category c where g.categoryId = c.id <!-- 一定要判断goods是否为空,否则会报错 source is null for getProperty(null, "categoryId") --> <if test="goods != null"> <!-- parameterType为hashmap的情况下,查询时一定要加key的名字,比如goods.name --> <if test="goods.categoryId != null and goods.categoryId!= '' "> and g.categoryId = #{goods.categoryId} </if> <!-- 这里要指出key值,因为hashmap中放了多个值,比如goods.name --> <if test="goods.name != null and goods.name != '' "> <!-- 模糊查询,1.要用bind 将查询的东西与页面的显示联系起来 --> <bind name="goods.name" value="'%' + goods.name + '%'" /> <!-- 模糊查询,2.like关键字模糊查询 --> and g.name like #{goods.name} </if> </if> ORDER BY id LIMIT #{pageNo}, #{pageSize} </select>
|
struts2 相关
struts2 文件上传
struts upload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| private File avatar; private String avatarFileName; public String uploadAvatar() { String userId = ((User) ActionContext.getContext().getSession() .get("user")).getId(); String path = ServletActionContext.getServletContext().getRealPath( "/userAvatars"); System.out.println(path); if (avatar != null) { String suffix = avatarFileName.substring(avatarFileName .lastIndexOf(".")); System.out.println(userId + suffix); File saveFile = new File(new File(path), userId + suffix); try { if (!saveFile.getParentFile().exists()) { saveFile.getParentFile().mkdir(); } FileUtils.copyFile(avatar, saveFile); } catch (IOException e) { e.printStackTrace(); } userService.upadteAvatar(userId, "userAvatars/" + userId + suffix); ((User) ActionContext.getContext().getSession().get("user")) .setAvatar("userAvatars/" + userId + suffix); return "usercenter"; } return path; }
|
struts得到界面的值
jsp中有captcha,要去后台验证是否正确
1 2
| <td><input style="width:85px" type='text' class='normal' name='captcha' /> <label>填写下图所示字符</label></td>
|
在action中可以直接定义该属性,要保持名字一致,并且使用set/get方法
1 2 3 4 5 6 7 8
| private String captcha; public String getCaptcha() { return captcha; } public void setCaptcha(String captcha) { this.captcha = captcha; }
|
strut2 验证码
注意将BufferedImage转化为ByteArrayInputStream,才能在前端显示出来
RandomAction.java后台验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class RandomAction { private ByteArrayInputStream image; public String execute(){ RandomUtils randomUtils = RandomUtils.Instance(); //验证码的图片 image = randomUtils.getImage(); //把验证码放入session中 ActionContext.getContext().getSession().put("vcode", randomUtils.getString()); System.out.println("vcode"+randomUtils.getString()); System.out.println("session"+ActionContext.getContext().getSession().get("vcode") .toString()); return "success"; } public ByteArrayInputStream getImage() { return image; } public void setImage(ByteArrayInputStream image) { this.image = image; } }
|
RandomUtils.java产生验证码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| public class RandomUtils { // public static final char[] CHARS = { '2', '3', '4', '5', '6', '7', '8', // '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', // 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; public static final char[] CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; private ByteArrayInputStream image;// 图像 private String str;// 验证码 private RandomUtils() { init();// 初始化属性 } public static RandomUtils Instance() { return new RandomUtils(); } /* * 取得验证码图片 */ public ByteArrayInputStream getImage() { return this.image; } /* * 取得图片的验证码 */ public String getString() { return this.str; } private void init() { // 在内存中创建图象 int width = 85, height = 20; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 获取图形上下文 Graphics g = image.getGraphics(); // 生成随机类 Random random = new Random(); // 设定背景色 g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); // 设定字体 g.setFont(new Font("Times New Roman", Font.PLAIN, 18)); // 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到 g.setColor(getRandColor(160, 200)); for (int i = 0; i < 155; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } // 取随机产生的认证码(6位数字) StringBuffer sRand = new StringBuffer(); for (int i = 0; i < 6; i++) { String rand = String.valueOf(CHARS[random.nextInt(CHARS.length)]); sRand.append(rand); // 将认证码显示到图象中 g.setColor(new Color(20 + random.nextInt(110), 20 + random .nextInt(110), 20 + random.nextInt(110))); // 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成 g.drawString(rand, 13 * i + 6, 16); } // 赋值验证码 this.str = sRand.toString(); // 图象生效 g.dispose(); ByteArrayInputStream input = null; ByteArrayOutputStream output = new ByteArrayOutputStream(); try { ImageOutputStream imageOut = ImageIO .createImageOutputStream(output); ImageIO.write(image, "JPEG", imageOut); imageOut.close(); input = new ByteArrayInputStream(output.toByteArray()); } catch (Exception e) { System.out.println("验证码图片产生出现错误:" + e.toString()); } this.image = input;/* 赋值图像 */ } /* * 给定范围获得随机颜色 */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > 255) fc = 255; if (bc > 255) bc = 255; int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
|
前端显示
1 2 3 4 5
| <tr> <th valign="middle">验证码:</th> <td><input style="width:85px" type='text' class='normal' name='captcha' /> <label>填写下图所示字符</label></td> </tr>
|
跳转
redirectAction直接跳转到action中
1 2 3 4
| <action name="address_*" class="addressAction" method="{1}"> <result name="oprsuc" type="redirectAction">address_list</result> <result name="list">/usercenter/address_list.jsp</result> </action>
|
spring相关
spring事务
一般在service层可以操作多个dao,因此要设置事务
service层,每个dao是一个对数据库的操作,下面这种2个dao一起添加的操作一定是事务
1 2 3 4 5 6
| public void addGoods(Goods goods) { goods.setId(MyUntil.getID()); goodsDao.addGoods(goods); //这是类别的更新,与商品添加一起完成 categoryDao.updateGoodsNum(goods.getCategoryId(), 1); }
|
添加c3p0,并在spring的xml文件中配置
不用添加任何其余代码就完成了事务的配置,基于AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <!-- 添加事务的dataSource --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/shoppingsite"></property> <property name="user" value="root"></property> <property name="password" value="mysql"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- REQUIRED表示不是事务就建立事务 --> <tx:method name="*" propagation="REQUIRED" /> <!--SUPPORTS不是事务就不建立 --> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <!-- 包名一定要写清楚,对应好 com.dy.shoppingSite.*.*(..)这个就不能对应,因为shoppingSite.*是包,也就没有方法--> <aop:pointcut id="pointcut" expression="execution(* com.dy.shoppingSite.service.impl.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" /> </aop:config>
|
jsp
c:forEach中的varStatus&el表达式计算比较等要放在括号内
c:forEach中的varStatus
varStatus有很多属性,index是序号,从0开始,也有first等
more:varStatus
- el表达式计算比较等要放在括号内
比如${string1 eq string 2}而不是${string1} eq ${string 2}1 2 3 4 5 6 7 8 9 10 11
| <c:set var="totalMoney" value="0"></c:set> <c:forEach items="${orderDetails}" var="orderDetail" varStatus="s"> <tr><td> <!-- //传递orderDetail要使用index,直接传是不成功的 --> <!-- 下标用s.index表示,http://www.opencms-wiki.org/wiki/C:forEach --> <input type="hidden" name="orderDetails[${s.index }].goods.id" value="${orderDetail.goods.id}"/> <input type="hidden" name="orderDetails[${s.index }].nums" value="${orderDetail.nums}" /> <b class="red2">${orderDetail.nums*orderDetail.goods.price2}</b> </td></tr> <c:set var="totalMoney" value="${totalMoney+orderDetail.nums*orderDetail.goods.price2}"/> </c:forEach>
|