Spring学习_day4~5

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种方式解决:

    1. 采用方法参数是一个对象的的形式,我们只需要保证这个对象中含有和请求参数名字相同的集合属性,以及含有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>
      
    2. 方法参数直接就是一个集合,这时候我们采用的是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

准备工作:

  1. 创建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 + '\'' +
                    '}';
        }
    }
    
    
  2. 创建对应的表user,role,user_role

  3. 创建对应的页面:login.jsp,dashboard.jsp,list.jsp等

要完成的工作:

  • 用户登录

    用户登录之前,我们需要对该用户的信息进行验证,如果输入的信息正确,那么就可以前往到对应的dashboard.jsp页面中,否则,将错误信息保存到request域中,然后转发到login.jsp页面。但是有几个地方需要注意的是:

    1. 如果这个用户没有办法在数据库中找到的时候,他并不是返回的是null,而是抛出的EmptyResultDataAccessException,所以我们不可以通过判断返回值User为null,从而证明无法找到这个用户,而是通过try-catch来解决,一旦捕捉到异常,就说明这个用户不存在,直接转发到login.jsp页面,并且给出错误提示
    2. 需要注意的是,如果我们利用注解@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层的
    3. 实现方法映射的时候,要在对应的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 = xxxsql语句。然后再执行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都删除,然后再重新插入即可

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值