Spring MVC获取请求参数的值
Spring获取请求参数的值,而请求参数的类型可以是基本的数据类型,也可以是数组,也可以是一个POJO对象,也可以是一个集合。
所以对于不同类型的参数,我们由不一样的方式进行获取:
-
请求参数是一个基本类型/数组,那么我们只需要保证方法参数和url中的请求参数的名字相同,那么就可以进行一一映射了,例如,url为
localhost:8080/test1?username=zhangshan&age=20
,而方法test1的参数的名字必须要和和url中的请求参数名字相同,即test1(String username,int age),这样,这个方法中的参数的值username=zhangshan,age=20.但是如果希望方法中的参数名字和url中的请求参数的名字不相同,那么这时候我们需要使用注解@RequestParam(value=xxx),这样xxx就会和url中请求参数名字为xxx的参数匹配。所以上面的test1方法可以改为test1(@RequestParam(“username”)String name,@RequestParam(“age”)int age),这样也可以实现我们目的。同理,如果请求参数是一个数组,即url为
localhost:8080/test2?strs=aaa&strs=bbb&strs=ccc
,这时候strs就是一个数组,然后我们对应的方法需要获取这个请求参数的值的时候,我们可以将方法参数名字也取为strs,也可以利用@RequestParam(“strs”)来实现映射,从而使得方法参数的值等于请求参数的值。对应的代码为:@Controller public class Controller3 { /* 获取请求参数的值,如果是基本的数据类型,那么只需要保证 请求参数的名字和方法中参数的名字相同,就可以获得到对应的值了 值得注意的是,如果方法的返回值是void,那么这时候我们需要要么 可以通过Spring MVC中的POJO,即HttpServletResponse,来调用对应的 方法进行响应,也可以在方法的上方写上注解@ResponseBody,此时 就告诉了Spring MVC这个是不需要进行页面跳转的,只需要进行回写数据 而因为返回值为void,所以没有任何数据可以进行回写 但是如果我们没有加上a@ResponseBody,那么这时候Spring MVC默认是要 进行页面跳转,此时由于返回值是void,那么就会自己转发给自己,从而陷入 到了死循环,从而发生了报错:would dispatch back to the current handler URL [/test1] again. (将再次调度回当前处理程序 URL [/test1])。 */ @RequestMapping("/test1") @ResponseBody public void test1(String username,int age){ System.out.println("username = " + username + ", age = " + age); } /* 如果请求参数是一个数组,那么我们需要获取这个数组的值的时候,那么我们需要保证 方法中的数组名字和请求参数的名字相同. 如果localhost:8080/test3?strs=aaa&strs=bbb&strs=ccc 并且请求参数是strs是个数组,并且元素为[aaa,bbb,ccc],所以为了获取到这个请求 参数的值,需要将方法中的参数名字是strs,并且是一个数组 */ @RequestMapping("/test3") @ResponseBody public void test3(String[] strs){ System.out.println(Arrays.asList(strs));//Arrays.asList,将数组转成List,然后输出 } /* 通过使用注解@RequestParam("xxx"),这样url中的请求参数的xxx就会赋值给 当前方法的参数yyy中,即url为localhost:8080/test6?username=111&age=13 的时候,那么注解应该是@RequestParam("username"),@RequestParam("age") 实现映射,然后这个注解修饰的参数的值就是url中对应的值了,修饰的参数名字 可以和上面url中的参数名字不同 值得注意的是@RequestParam注解含有3个参数: - value = "xxx",如果只有一个属性的时候,可以省略不写,如上面的@RequestParam("xxx" 就是省略了value这个属性,不省略时为@ReqeustParam(value="xxx") - required,判断这个参数是否为必须的,默认的是true,表示url中一定需要带有请求参数 如果为false,url中可以没有这个名字的请求参数 - defaultValue:如果没有带有这个请求参数的时候,那么它的默认值就是defaultValue的值 */ @RequestMapping("/test6") @ResponseBody public void test6(@RequestParam("username")String name, @RequestParam(value="age",defaultValue="20")int age){ System.out.println("username = " + name + ", age = " + age); } @RequestMapping("/test9") @ResponseBody public void test9(@RequestParam("strs")String[] strings){ System.out.println(Arrays.asList(strings)); } }
-
如果方法的参数是一个对象,例如User的时候,也即我们希望将url中的请求参数封装到一个对象中,那么这时候我们只需要将对象User中的属性成员的名字和请求参数的名字相同,并且这个对象含有get/set方法,这样我们就可以将请求参数的值封装到一个对象中。对应代码如下所示:
-
如果请求的参数是一个集合,那么这时候我们可以有2种方式解决:
-
采用方法参数是一个对象的的形式,我们只需要保证这个对象中含有和请求参数名字相同的集合属性,以及含有set/get方法即可,对应的代码为:
@RequestMapping("/test4") @ResponseBody public void test4(Vo vo){ System.out.println(vo); } //Vo类 public class Vo { private List<User> list; public void setList(List<User> list) { this.list = list; } public List<User> getList() { return list; } @Override public String toString() { return "Vo{" + "list=" + list + '}'; } }
对应的user.jsp代码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>获取表单中的多个user</title> </head> <body> <form action="${pageContext.request.contextPath}/test4" method="post"> <!-- 如果提交表单的时候,那么就会发送的是test4的请求, 此时就会给controller3中的test4方法发送请求 --> <table> <thead> <td>姓名</td> <td>年龄</td> </thead> <tr> <!-- 因为controller3中的test4方法参数是Vo类 而Vo类中含有名称为list的属性,所以这时候 的input中的list刚好和Vo类中的名称为list的属性 对应。因此list[0]就是Vo中list中的第一个元素User list[0].name则是第一个元素User中名称为name的属性 --> <td><input type="text" name="list[0].name"></td> <td><input type="text" name="list[0].age"></td> </tr> <tr> <td><input type="text" name="list[1].name"></td> <td><input type="text" name="list[1].age"></td> </tr> </table> <input type="submit" value="提交"> </form> </body> </html>
但是如果我们输入的数据中含有中文的时候,那么这时候,就会发现后台数据是发生了乱码,这时候我们需要解决的是全局的乱码问题,而要解决全局乱码问题,可以通过spring mvc的CharacterEncodingFilter来解决,不过我们需要在web.xml中配置这个过滤器,只要将下面的代码添加到web.xml中即可:
<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <!--url-pattern的值为/*,表示对所有的请求都需要进行拦截,注意不可以是/,否则依旧会发生乱码--> <url-pattern>/*</url-pattern> </filter-mapping>
-
方法参数直接就是一个集合,这时候我们采用的是ajax自动提交的方式进行请求,这时候我们只需要保证方法参数被@RequestBody修饰即可.这时候我们需要创建一个ajax.jsp文件,对应的代码为:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>获取请求参数为集合类型的值的第二种方式</title> <script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script> <script> var list = new Array(); list.push({"name":"张山",age:20}); list.push({"name":"小明",age:18});//list中的元素是User,user类中的属性名字分别为name,age $.ajax({ url:"${pageContext.request.contextPath}/test5", //将请求到controller3中test5 type:"POST", //请求的方法是POST data:JSON.stringify(list),//将list转成json格式字符串 contentType:"application/json;charset=utf-8" }) </script> </head> <body> </body> </html>
对应的jquery-3.3.1.js文件可以从网上下载,并且将其放在web/js下面(js目录是自己创建的)
对应的controller3中的test5代码为:
/* 获取请求参数类型为集合的值方法二: 通过ajax自动提交,这时候如果它的请求方法是Post,那么它可以发送数据, 这时候我们就需要利用注解@RequestBody,获取请求实体中的数据将这个注 解放在方法参数的前面,就可以将请求实体的数据赋值给这个方法参数 */ @RequestMapping("/test5") @ResponseBody public void test5(@RequestBody List<User> userList){ System.out.println(userList); }
正常情况下,运行的时候,会因为没有找到jquery-3.3.1.js文件,从而导致ajax.jsp中的
$
is not defined。原因是Spring MVC没有办法找到静态资源js/jquery-3.3.1.js,所以为了解决这个问题,我们需要在spring-mvc中进行配置。但是Spring Boot中已经默认帮我们配置了,所以上面的代码是可以正常运行的。但是如果我们需要导入静态资源,那么需要在spring-mvc中通过添加下面的任意一行代码到spring-mvc.xml中即可:
<!--开放资源的访问--> <mvc:resources mapping="/js/**" location="/js/"/> <!--开放资源的访问,其中mapping是映射到js目录下面的任意文件,而location说明的是文件的位置--> <!--如果在Servlet中没有找到对应的资源,那么就会来到默认的Servlet中寻找--> <mvc:default-servlet-handler/>
导入上面任意一行的代码,可以实现静态资源的导入.
-
如果url是诸如
localhost:8080/test9/张三
的形式,其中url中的张三
是请求参数的值,那么这时候我们要怎样获取到请求参数的值呢?此时为了和使得url和某一个controller类中的某一个方法成功映射,我们可以这样做:@RequestMapping(“/test9/{username}”),这样就可以使得前面说到的url和当前注解修饰的方法映射,并且请求参数username的值为张三。这时候我们需要使用@PathVariable(“username”),就可以将请求参数的值赋值给方法参数,对应的代码如下所示:/* 如果url的格式为localhost:8080/test7/张三(张三是请求参数的值) 那么这时候我们@RequestMapping要和对应的方法进行映射的时候,同样 需要加上参数的值,即@RequestMapping("/test7/{username}") 这时候如果需要获取请求参数的值,那么需要使用注解@PathVariable("username") 才可以将请求参数的值赋值给方法中的参数 */ @RequestMapping("/test7/{username}") @ResponseBody public void test7(@PathVariable("username")String username){ System.out.println(username); }
在上面的案例test1中,对应的代码为:
@ResponseBody public void test1(String username,int age){ System.out.println("username = " + username + ", age = " + age); }
方法的参数是int类型的,但是url中的请求参数是String类型,为什么这样没有发生报错呢?这是因为Spring MVC中含有自动类型转换器,能够帮我们自动进行类型转换,但是请求参数是Spring MVC不能够自动转换的类型的时候,例如Date=2020-10-11(它默认的是2020/10/11的形式),那么这时候需要我们来自己定义了,对应的步骤为:
- 创建一个类Converter,使得这个类实现了接口Converter<S,T>其中这个接口表示的是S类型转变成为了T类型,并且在Converter类中,实现它的方法converts
- 在spring-mvc中,将上面创建的Converter添加到ConvertionServiceFactoryBean的converters属性中
- 然后通过spring-mvc的mvc注解驱动,从而设置convertion-service属性的值。对应的代码如下:
<mvc:annotation-driven conversion-service="conversionService"/> <!--配置转换器--> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <!--添加自动的转换器到这里--> <bean class="day5.demo.converter.DateConverter"></bean> </list> </property> </bean>
对应的DateConverter代码为:
public class DateConverter implements Converter<String, Date> { @Override public Date convert(String s) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = simpleDateFormat.parse(s); } catch (ParseException e) { e.printStackTrace(); } return date; } }
controller3中的代码test8为:
/* 采用请求参数的名字和方法参数的名字相同,从而方法参数和请求参数一一映射 所以如果url为localhost:8080/test8?date=2020-1-20的时候,方法参数就可以 获得date=2020-1-20的值了(因为上面的DateConverter已经可以将这样形式的日期进行 转换了,但是不可以将date=2020/1/20的形式的日期转换,否则就会发生报错,并且输出的date 为null),因为simpleDateFormat调用的parse方法中抛出了ParseException: Unparseable date 异常 */ @RequestMapping("/test8") @ResponseBody public void test8(Date date){ System.out.println(date); }
-
Spring MVC的文件上传
文件上传中,表单的要求为:
- 表单项中含有file,即含有
<input type="file" name="xxxx">
的标签 - 表单的method属性为post
- 表单的enctype属性要等于
multipart/form-data
这时候,因为enctype的属性值为multipart/form-data
,所以request调用getParameter来获取请求参数的值为null,因此不可以通过调用getParameter来获取表单项的值。因此我们需要利用FileUpload来获取表单项的元素。因此剩下操作应该是:
-
导入commons-fileupload,commons-io的依赖到pom.xml文件中,对应的代码为:
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
-
在spring-mvc.xml中配置文件上传解析器,对应的代码为:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="500000000"></property><!--上传文件的总大小--> <property name="maxUploadSizePerFile" value="50000000"></property><!--单个文件的上传大小--> <property name="defaultEncoding" value="utf-8"></property> <!--设置编码--> </bean>
-
根据请求参数和方法参数名称一致的时候,请求参数的值就会赋值给方法参数,那么这时候我们就可以编写代码了。对应的代码:
@RequestMapping("/test10") @ResponseBody public void test10(String username,MultipartFile[] uploadFiles) throws IOException { System.out.println(username);//普通表单项的值 //文件表单项调用getName,获取的是表单项在jsp文件中的name,而不是文件名字 //要获取上传的文件名字,需要调用getOriginalFileName才可以 File dir = new File("E://IDEAworkspace/代码/Spring学习/upload/"); if(!dir.exists()){ dir.mkdir(); } for(MultipartFile file : uploadFiles){ String filename = file.getOriginalFilename(); System.out.println(filename); //将调用transferTo,从而将当前的文件表单项复制到file中 file.transferTo(new File( dir,filename)); } }
我们需要先访问upload.jsp页面,来选择要上传的文件,当点击提交按钮之后,聚会来到test10方法,所以upload.jsp的代码为:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件上传</title> </head> <body> <form method="post" action="${pageContext.request.contextPath}/test10" enctype="multipart/form-data"> <!--文件上传表单的要求: method属性为post enctype属性值为multipart/form-data 含有file表单项。 为了获取表单项的元素,spring mvc中要求请求参数的名字和映射到的方法参数的名字要一致, 这样方法参数就可以获取请求参数的值了。 --> 名字: <input type="text" name="username"><br> 文件1:<input type="file" name="uploadFiles"><br> 文件2:<input type="file" name="uploadFiles"><br> 文件3:<input type="file" name="uploadFiles"><br> <input type="submit" value="提交"> </form> </body> </html>
当我们输入:
localhost:8080/upload.jsp
的时候,我们来到对应的界面为:
点击提交之后,就会将对应的文件上传到了upload目录中:
Spring MVC中JdbcTemplate的操作
通过使用JdbcTemplate,可以简化一些操作,不象之前操作那样麻烦。所以,利用JdbcTemplate操作之前所需要做的准备:
-
导入spring-jdbc和spring-tx的依赖到pom.xml中,并且因为需要连接数据库,同样需要导入java-connector-mysql依赖,并且还需要配置c3p0数据源,所以还需要再导入c3p0依赖
-
由于我们需要利用spring整合junit进行测试,所以还需要再导入spring-test以及junit依赖,所以对应的代码为:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.8.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency>
-
当依赖导入完毕之后,我们要创建对应的表account,以及对应的类Account,这个表中的列主要是name以及balance.分别是String以及Integer类型。对应的代码为:
public class Account { private String name; private Integer balance; public Account() { } public Account(String name, Integer balance) { this.name = name; this.balance = balance; } @Override public String toString() { return "Account{" + "name='" + name + '\'' + ", balance=" + balance + '}'; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getBalance() { return balance; } public void setBalance(Integer balance) { this.balance = balance; } }
-
通过
<bean id=xxxx class=yyyy></bean>
的形式,将JdbcTemplate,ComboPooleadDataSource添加到Spring MVC容器中,并且设置ComboPooleadDataSource中的driverClass,JdbcUrl,user,password的属性。同时我们需要为JdbcTemplate配置属性dataSource是ComboPooleadDataSource.所以对应的applicationContext.xml代码为:<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--利用context命名空间,来加载外部的properties文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="c3p0DataSource"></property> </bean> </beans>
对应的jdbc.properties代码为:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=root
完成上述的操作之后,就可以进行测试了。其中JdbcTemplate主要有2个方法:
-
update:用来实现增删改操作的
-
query:用来实现查询的操作。如果希望返回的是单个对象,那么我们需要执行的是queryForObject(String sql,RowMapper)方法,其中我们需要将定义一个类XXXRowMapper,来实现RowMapper这个接口,这样,返回的是才是我们想要的对象。同样,如果执行的是query(String sql,RowMapper),这时候返回的是一个List,如果希望返回的是List<XXX>,那么同样的,我们传递的RowMapper参数是我们自定义的。
当然,我们也可以不用自定义RowMappper,我们直接传递BeanPropertyRowMapper<xxx>(xxx.class)即可。所以如果我们需要获取List<Account>,调用的是query方法,这时候我们有2中方式可以获取:
①query(sql,new AccountRowMapper()),自定义RowMapper;
②传递参数BeanPropertyRowMapper<Account>(Account.class)即可,所以只需要调用**query(sql,new BeanPropertyRowMapper<Account>(Account.class))**方法即可
同理,如果我们需要获取的是单个Account对象,那么我们也是有2中方式获取,一种是传递自定义的RowMapper对象,一种是传递BeanPropertyRowMapper<XXX>(XXX.class)对象,只是这时候是调用queryForObject方法了:
①queryForObject(sql,new AccountRowMapper()),自定义RowMapper;
②传递参数BeanPropertyRowMapper<Account>(Account.class)即可,所以只需要调用**queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class))方法即可
如果我们希望要获取多少行之类的数据,这时候返回的是一个数字,此时调用queryForObject方法,并且返回的数据是一个基本的数据类型,并不需要我们进行封装,所以传递就是对应的类型的字节码对象即可。例如queryForObject(sql,Long.class)。
测试类JdbcTemplateTest代码为:
/*
Spring整合Junit4测试
- 导入spring mvc依赖
- 使用注解@RunWith(SpringJunit2),使得运行期代替运行时
- 使用注解@Configuration,来读取核心配置文件
- 使用注解@Autowired,来则是对应的bean
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateTest {
@Test
public void test1() throws Exception {
//配置ComboPooledDataSource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai");
dataSource.setUser("root");
dataSource.setPassword("lf_MySQL");
//创建JdbcTemplate对象,并且设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
String sql = "insert into account(name,balance) values (?,?)";
Object[] objs = new Object[]{"小王",5000};
int row = jdbcTemplate.update(sql, objs);
System.out.println(row);//返回值如果不为0,说明sql语句操作成功
}
/*
利用的是@Autowired,来获取Spring MVC中的JdbcTemplate实例
但是因为Spring MVC的容器中没有JdbcTemplate实例化对象,所以
不可以直接利用@Autowired来直接获取JdbcTemplate实例化对象。
所以我们需要在applicationContext.xml中添加JdbcTemplate到Spring MVC容器中
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
@Qualifier("c3p0DataSource")
private ComboPooledDataSource dataSource;
*/
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Test
public void test2(){
//利用jdbcTemplate调用update方法进行修改操作
String sql = "update account set balance = balance + 50000 where name = ?";
int row = jdbcTemplate.update(sql, "小王");
System.out.println(row);//如果不等于0,说明编辑成功
}
//利用JdbcTemplate来进行删除操作
@Test
public void test3(){
String sql = "delete from account where name = ?";
int row = jdbcTemplate.update(sql, "小王");
System.out.println(row);//row不等于0的时候,说明删除操作成功
}
@Test
public void test4(){
//查询单个Account用户
String sql = "select * from account where name = ?";
Account account = (Account)jdbcTemplate.queryForObject(sql, new AccountRowMapper(), "AA");
System.out.println(account);
}
@Test
public void test5(){
//查询多个Account用户
String sql = "select * from account";
List<Account> accounts = jdbcTemplate.query(sql, new AccountRowMapper());
System.out.println(accounts);
}
@Test
public void test6(){
//查询多个用户的方式2
String sql = "select * from account";
List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accounts);
}
@Test
public void test7(){
//查询单个用户的方式2
String sql = "select * from account where name = ?";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), "AA");
System.out.println(account);
}
//查询有多少行
@Test
public void test8(){
String sql = "select count(*) from account";
//因为返回的是一个数据,所以调用queryForObject即可
//并且返回值是一个数据类型,所以不需要封装了
Long count = jdbcTemplate.queryForObject(sql, Long.class);
System.out.println(count);
}
}
对应的AccountRowMapper代码为:
public class AccountRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setName(resultSet.getString("name")); //name在数据库表中是String类型的,所以调用的是getString
account.setBalance(resultSet.getInt("balance"));//balance在数据库表中的类型的是int类型的,所以调用的是getInt
/*
当然也可以调用的是resultSet.getString(下标)来获取,注意的是下标是从1开始的,并且
这些下标是根据要执行的sql语句中获取的值得先后顺序来的,而不是根据数据库表中列的先后
顺序为依据的。
以当前account表为例,如果sql语句为select balance,name from account,那么这时候
balance的下标为1,name的下标为2
但是如果sql语句为select * from account,这时候就是依据数据库表中的列的顺序为依据,所以
这时候name的下标为1,balance的下标为2
*/
return account;
}
}
Spring练习
这里主要通过练习一个管理系统(主要是管理用户的增删改查).
技术:Spring + MySQL + jsp
准备工作:
-
创建domain包下的User类以及Role类,对应的代码
public class User { private Integer id; private String name; private List<Role> roles; private String email; private String phone; private String password; public User() { } public User(Integer id, String name, List<Role> roles, String email, String phone, String password) { this.id = id; this.name = name; this.roles = roles; this.email = email; this.phone = phone; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", roles=" + roles + ", email='" + email + '\'' + ", phone='" + phone + '\'' + ", password='" + password + '\'' + '}'; } }
Role代码:
public class Role { private Integer id; private String name; public Role() { } public Role(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Role{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
-
创建对应的表user,role,user_role
-
创建对应的页面:login.jsp,dashboard.jsp,list.jsp等
要完成的工作:
-
用户登录
用户登录之前,我们需要对该用户的信息进行验证,如果输入的信息正确,那么就可以前往到对应的dashboard.jsp页面中,否则,将错误信息保存到request域中,然后转发到login.jsp页面。但是有几个地方需要注意的是:
- 如果这个用户没有办法在数据库中找到的时候,他并不是返回的是null,而是抛出的EmptyResultDataAccessException,所以我们不可以通过判断返回值User为null,从而证明无法找到这个用户,而是通过try-catch来解决,一旦捕捉到异常,就说明这个用户不存在,直接转发到login.jsp页面,并且给出错误提示。
- 需要注意的是,如果我们利用注解@Repository,@Service,@Controller,将对应的类添加到spring mvc容器的时候,需要注意组件的扫描。所以我们需要在applicationContext.xml,spring-mvc.xml中利用context命名空间,执行
<context:component-scan base-package="xxx"/>
进行组件扫描,其中applicationContext.xml用来扫描service,dao等其他层的,而spring-mvc.xml则是扫描controller层的 - 实现方法映射的时候,要在对应的Controller类中添加@Controller注解,这样,才可以扫描各个组件,找到能够映射的方法,否则,如果下面的LoginController没有添加@Controller注解,那么这时候Spring MVC容器中没有这个Bean对象,那么就没有办法映射到对应的方法了。
思路很简单,对应的代码为:
@Controller //利用@Controller,从而将controller层中的类添加到Spring容器中 @RequestMapping("/loginController") public class LoginController { @Autowired private UserService userService; /* 因为在类上方使用了注解@RequestMapping,所以url为localhost:8080/loginController/login, 才可以映射到这个方法,并且如果返回值没有写/符号,直接写return login.jsp,那么就会提示404 错误,因此我们需要在前面加/符号才可以解决 */ @RequestMapping("/login") public String login(String username, String password, HttpServletRequest request){ //判断是否为空 if(username == null || username.length() <= 0){ request.setAttribute("msg","用户名不可以为空"); /* 值得注意的是,如果这里是这样写的话:return "login.jsp", 那么这时候跳转的时候,请求的是loginController/login.jsp页面 此时就会因为找不到这个页面,从而发生404错误 所以需要在前面加上一个/符号,表示在当前的页面 */ return "/login.jsp"; } if(password == null || password.length() <= 0){ request.setAttribute("msg","密码不可以为空"); return "/login.jsp"; } //利用userService,来查找这个姓名的用户 try{ User user = userService.query(username, password); //该用户存在,那么直接重定向到首页,并且将这个用户保存到session域中 HttpSession session = request.getSession(); session.setAttribute("user",user); return "redirect:/dashboard.jsp"; }catch(EmptyResultDataAccessException e){ request.setAttribute("msg","用户或者密码不存在"); request.setAttribute("username",username); request.setAttribute("password",password); return "login.jsp"; } } }
对应的UserService代码:
public interface UserService { public User query(String username, String password); } @Service("userService") //利用注解@Service,从而将这个类添加到Spring 容器中 public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User query(String username, String password) throws EmptyResultDataAccessException { return userDao.query(username,password); } }
对应的UserDao代码:
public interface UserDao { User query(String username, String password); } @Repository("userDao") //利用注解@Repository,从而将Dao层中的类添加到Spring容器中 public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public User query(String username, String password) throws EmptyResultDataAccessException { String sql = "select * from user where name = ? and password = ?"; User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username, password); if(user != null){ sql = "select role.id,role.name from role inner join user_role " + " on role.id = user_role.role_id " + " where user_role.user_id = ?"; List<Role> roleList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Role>(Role.class), user.getId()); user.setRoles(roleList); } return user; } }
-
用户退出登录:主要是通过Session调用invalidate方法,从而注销当前的session,然后重定向到login.jsp页面,对应的代码为:
@RequestMapping("/logout") public String logout(HttpServletRequest request){ HttpSession session = request.getSession(); session.invalidate(); //重定向到login.jsp页面 return "redirect:/login.jsp"; }
-
用户查询以及删除
获取所有的用户的时候,由于User的身份可能不只一个,所以这时候我们在User中定义一个List<Role>,而同样的,一个Role对应的用户也不只一个,所以这时候仅仅靠2张表user和role是不能够满足的,因此我们还需要定义第三张表role_user,从而实现多对多的关系。
那么这时候我们要获取所有用户的时候,此时需要先知道user的id,然后根据user的id,来获取这个user对应的role_id,然后再查询role表,得到对应role_id的Role对象。然后再封装到对应的User中。
各个表之间的关系为:
而这时候需要注意的是,假设我们已经知道了User的id,那么这时候我们如果想知道这个User对应的role_id那么我们要查询表user_role,也即执行
select role_id from user_role where user_id = xxx
sql语句。然后再执行select * from role where id = ?
获取Role。获取user的id之后,因为需要获取多个List<Integer>,所以需要通过jdbcTemplate调用query(sql,new BeanPropertyRowMapper<Integer>(Integer.class))来获取。然而,这时候就会发生报错:
Failed to instantiate [java.lang.Integer]
,这是因为在Spring MVC中,只有需要获取自定义的对象的时候,才需要传递参数BeanPropertyRowMapper<xxxx>(xxx.class),而这时候Spring MVC中已经有了Integer这些类了,那么只能传递对应的字节码对象。但是传递字节码对象的query,只有queryForObject.所以这时候我们应该考虑联结的sql语句来解决。所以我们要获取List<Role>,假设我们已经知道了用户的id,则对应的sql语句为:
String sql = "select role.id,role.name from " " role inner join user_role " + " on role.id = user_role.role_id " + " where user_role.user_id = ?";
这样执行这个sql语句之后,就可以获取到对应的用户的List<Role>了。
而在获取所有的user之后,我们需要返回到页面,并且展示用户列表,这时候我们需要通过el表达式来实现,通过标签
c:forEach
进行遍历即可。所以对应的list.jsp代码为:<a href="${pageContext.request.contextPath}/userController/preAdd" class="btn btn-lg btn-primary">新增</a> <div class="table-responsive"> <table class="table table-striped table-sm"> <thead> <td>姓名</td> <td>邮箱</td> <td>角色</td> <td>电话</td> <td>操作</td> </thead> <c:forEach items="${requestScope.users}" var="user"> <tr> <td>${user.name}</td> <td>${user.email}</td> <td> <c:forEach items="${user.roles}" var="role"> ${role.name} </c:forEach> </td> <td>${user.phone}</td> <td> <a href="${pageContext.request.contextPath}/userController/preUpdateById?id=${user.id}">编辑</a> <a href="${pageContext.request.contextPath}/userController/deleteById?id=${user.id}">删除</a> </td> </tr> </c:forEach> </tbody> </table>
效果展示如下所示:
-
用户的添加
对于用户的添加,首先我们点击新增之后,就会来到add.jsp页面,在这个页面中,我们需要输入要新添加的用户的信息,点击提交之后,才会将这个用户添加到数据库中,并且重新执行queryAll操作。
而在添加操作中,因为需要知道所有用户的信息,所以我们在跳转到add.jsp页面之前,需要先获取所有的角色信息,然后再跳转到add.jsp页面。
因为用户的角色可能不只一个,所以需要利用复选框。为了能够将提交的数据封装到一个User中,表单项的名字需要和User中的属性名字相同。由于在角色选中,采用的是复选框,那么我们这时候选中的角色需要用一个数组来封装,所以复选框中的name标签的值都相同,value属性则是每个角色对应的id。
所以对应的add.jsp主要代码为:
<div class="table-responsive"> <form class="table table-striped table-sm" action="${pageContext.request.contextPath}/userController/add"> <input type="hidden" name="password" value="123456"> <!--默认的密码为123456--> 姓名: <input type="text" name="name"><br> 邮箱: <input type="text" name="email"><br> 电话: <input type="text" name="phone"><br> 角色: <c:forEach items="${requestScope.roles}" var="role"> <input type="checkbox" name="role_ids" value="${role.id}">${role.name} </c:forEach><br> <input type="submit" value="提交"> </form> </div>
对应的UserController中的添加操作的代码为:
@RequestMapping("/preAdd") public String preAdd(HttpServletRequest request){ //获取所有的角色 List<Role> roles = roleService.queryAll(); request.setAttribute("roles",roles); return "/add.jsp";//转发到add.jsp页面 } @RequestMapping("/add") public String add(User user,int[] role_ids){ //add.jsp中的每一个表单项的名字对应的是User中的成员名字,并且role_ids对应的是复选框的name属性的值 userService.add(user,role_ids); return "queryAll"; }
对应UserDao代码为:
@Override public void add(User user, int[] role_ids) { String sql = "insert into user (name,email,phone,password) values (?,?,?,?)"; jdbcTemplate.update(sql, user.getName(), user.getEmail(), user.getPhone(), user.getPassword()); //获取最新添加的用户的id sql = "select MAX(id) from user"; Integer user_id = jdbcTemplate.queryForObject(sql, Integer.class); sql = "insert into user_role (user_id,role_id) values (?,?)"; for(int role_id : role_ids){ jdbcTemplate.update(sql,user_id,role_id); } }
这里之所以不可以直接通过User来获取id,是因为上面我们添加用户的时候,并没有传递用户的id,所以这时候封装的User中的id是null。而因为将这个用户添加到数据库中,那么这个用户对应的就是最大的id,所以只要通过执行sql语句
select MAX(id) from user
,就可以获得新添加的用户的id了。测试结果如下所示:
这时候,我们新添加的数据发生了乱码,这时候我们需要设置全局编码来解决,而再spring mvc中可以通过CharacterEncodingFilter来解决,对应的代码为:<filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <!--url-pattern的值为/*,表示对所有的请求都需要进行拦截,注意不可以是/,否则 依旧会发生乱码 --> <url-pattern>/*</url-pattern> </filter-mapping>
这样就解决了乱码问题了。
-
用户的编辑
因为这个用户的角色是多对多的关系,所以一个用户可能会有多个角色。所以在进行编辑之前,首先需要获取所有的role,然后再来到update.jsp页面进行编辑。而编辑的时候,需要显示该用户的信息,所以在编辑之前,同样需要获取这个用户的信息。
这时候,角色的显示,因为可能是由多个角色,所以需要利用
<c:forEach></c:forEach
进行遍历,打印所有的role。然后需要根据当前的用户,设置哪些角色是当前用户对应的角色。所以当复选框中含有checked
属性,那么这个复选框就是默认选中的。也即**<input type="checkbox" name="xxx" value="yyy" checked>
就是默认选中,这时候只有由checked,那就是默认选中,不管checked的值是什么,如果没有checked,那么就是没有默认选中**。对应的代码为:
<form class="table table-striped table-sm" action="${pageContext.request.contextPath}/userController/updateById"> <input type="hidden" name="id" value="${requestScope.user.id}"> 姓名: <input type="text" name="name" value="${requestScope.user.name}"><br> 邮箱: <input type="text" name="email" value="${requestScope.user.email}"><br> 电话: <input type="text" name="phone" value="${requestScope.user.phone}"><br> 角色: <c:forEach items="${requestScope.user.roles}" var="user_role"> <c:forEach items="${requestScope.roles}" var="role"> <c:choose> <c:when test="${role.id == user_role.id}"> <input type="checkbox" name="role_ids" value="${role.id}" checked>${role.name} </c:when> <c:otherwise> <input type="checkbox" name="role_ids" value="${role.id}">${role.name} </c:otherwise> </c:choose> </c:forEach> </c:forEach><br> <input type="submit" value="提交"> </form>
但是角色那里进行展示的时候,却不是我们想要的结果,如下图所示:
所有的角色都重复了3次,所以我们需要避免这种情况,但是怎么避免,暂时还没有想到😭。所以只能没有设置默认选中。也即复选框中代码为:
<c:forEach items="${requestScope.roles}" var="role">
<input type="checkbox" name="role_ids" value="${role.id}">${role.name}
</c:forEach><br>
此时编辑界面不在支持默认选中的功能,然后我们提交之后,需要将编辑好的信息封装到User中,并且选中的role_id封装到一个数组中,然后在数据库中进行编辑操作。这时候我们先将当前用户原来的role都删除,然后再重新插入即可。