编写DAO,通过JdbcTemplate操作数据库的实践

目的:编写DAO,通过Spring中的JdbcTemplate,对数据库中的学生数据进行增删改查操作。

要操作的数据库表结构为:

 

一、大体框架

1.要利用JdbcTemplate,首先要添加Spring依赖。用quickstart模板创建Maven项目,在pom.xml中添加Spring依赖:

2.创建学生类(数据传递类),描述学生信息。

3.创建数据操作接口,规定需要哪些方法来操作数据。

增、改各一种方法,删、查各4中方法:通过id,name,专业+学号,Student对象(方便出现前三种方法以外的需求时删、查)。

4.创建数据操作类,实现数据操作接口

5.创建DAO工厂类,用于创建数据操作对象(日后有新的数据操作类时,只需在此修改)

6.测试类

 

二、通过JdbcTemplate操作数据库

为了让JdbcTemplate正常工作,只需要为其设置DataSource就可以了。

1.数据源DataSource

(1)基于Jdbc驱动的数据源

Spring提供了三个通过Jdbc驱动定义数据源的数据源类(org.springframework.jdbc.datasource.*):

  • DriverManagerDataSource类:每次连接请求时返回一个新的链接。
  • SimpleDriverDataSource类:同上,但直接使用Jdbc驱动,未解决特定环境下的类加载问题。
  • SingleConnectionDataSource类:每次连接请求时返回同一个链接。可以看作只有一个链接的连接池。

【这三个数据源类一般用于测试,项目上用数据源连接池是更好的选择】

配置方法:

①Java程序中配置:【例】

 1 //数据库配置存放在配置文件db.properties中
 2 
 3 //载入db.properties
 4 Properties sqlConfig = new Properties();
 5 sqlConfig.load(new FileInputStream("db.properties"));    //抛出IOException(FileNotFoundException)
 6 //不抛出Exception的方法:
 7 //sqlConfig.load(本类类名.getClassLoader().getResourceAsStream("db.properties"));
 8 
 9 //读取db.properties中的数据
10 String driver = sqlConfig.getProperty("driver");
11 String url = sqlConfig.getProperty("url");
12 String username = sqlConfig.getProperty("username");
13 String password = sqlConfig.getProperty("password");
14 
15 //配置数据源对象
16 DriverManagerDataSource ds = new DriverManagerDataSource();
17 ds.setDriverClassName(driver);
18 ds.setUrl(url);
19 ds.setUsername(username);
20 ds.setPassword(password);

②xml文件中配置

<context:property-placeholder location="classpath:db.properties">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="${driver}"
    p:url="${url}"
    p:username="${username}"
    p:password="${password}"    />

(2)数据源连接池

Spring中并没有提供数据源连接池的实现,但我们可以利用开源实现:

  • Apache Commons DBCP:http://jakarta.apache.org/commons/dbcp
  • c3p0:http://sourceforge.NET/projects/c3p0/
  • BoneCP:http://jolbox.com/

这些数据源的配置方法与(1)中基于Jdbc的数据源类似:

①Java程序中配置:【以DBCP为例】

在Maven中添加依赖后:

(前面的获取db.properties中数据的方法相同,此处不再赘述)

1 //配置数据源对象
2 BasicDataSource ds = new BasicDataSource();
3 ds.setDriverClassName(driver);
4 ds.setUrl(url);
5 ds.setUsername(username);
6 ds.setPassword(password);
7 ds.setInitialSize(5);   //池配置属性
8 ds.setMaxActive(10);

BasicDataSource中的池配置属性:

 

②xml文件中配置

 

<context:property-placeholder location="classpath:db.properties">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
    p:driverClassName="${driver}"
    p:url="${url}"
    p:username="${username}"
    p:password="${password}"
    p:initialSize="5"
    p:maxActive="10"    />

 

2.创建JdbcTemplate对象(将DataSource注入到jdbcTemplate实例)

(1)Java程序中完成

通过1中程序获取dataSource实例后:

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

这里的dataSource可以是javax.sql.DataSource的任意实现。

(2)xml文件完成
通过Spring的依赖注入,将dataSource注入到jdbcTemplate中:

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
    <property name="dataSource" ref="dataSource"></property>  
</bean>  
 

然后在程序中通过getBean获取这个jdbcTemplate:

ApplicationContext apc = new ClassPathXmlApplicationContext("xxx.xml");
JdbcTemplate jdbcTemplate = (JdbcTemplate) apc.getBean("jdbcTemplate");

3.使用JdbcTemplate中的方法操作数据

在调用相关函数时,将sql语句中要操作的值写为?,然后在后面加上对应的值作为参数。如果是单个值,可直接作为参数传递;如果是多个值,可以用参数数组的形式传递:new Object[]{值1,值2,...}。
比如:
要插入一个name数据,可以这样写:

jdbcTemplate.update("INSERT INTO student (name) VALUES (?)","张三");
 

要修改姓名和qq,可以这样写:

jdbcTemplate.update("UPDATE student SET name=?,qq=? WHERE id=?",new Object[]{"李四","123456789",45});

值得一提的是,程序中的sql语句并不需要写分号作为结尾。

JdbcTemplate中的常用方法:

  • excute(sql):可以用于执行任何sql语句,一般用于执行DDL语句(数据定义语言,用于定义、修改、删除数据库/表)【返回:void】
  • update(sql,Object[]):执行增删改sql语句【返回:int,影响的行数】
  • batchUpdate(sql,List<Object[]>):执行批处理增删改sql语句【返回:int[],受每个语句影响的行数】
  • query(sql,RowMapper<T>,Object)或(sql,Object[],RowMapper<T>):执行sql查询语句,通过RowMapper将每个行映射到一个java对象。【返回:List<T>,结果列表(映射对象)】
  • queryForObject(sql,RowMapper<T>,Object)或(sql,Object[],RowMapper<T>):执行sql查询语句,通过RowMapper将单个结果映射到java对象。【返回:T,映射对象】
查询函数中的RowMapper<T>:用于将查询结果映射到java对象。一般是自定义出一个RowMapper,复写其中的mapRow方法(T mapRow(ResultSet rs,int rowNum)),在方法中将当前行的各项数据赋给java对象中的对应属性。
 
三、全部流程
1.创建Maven项目,在pom.xml中添加依赖:
Spring中各个jar包的作用详见:Spring中各jar包的作用
这里应添加的jar包是:
  • Spring:spring-context,spring-core,spring-jdbc,spring-tx
  • 数据源:DBCP
  • Mysql驱动
 1   <dependencies>
 2     <dependency>
 3       <groupId>junit</groupId>
 4       <artifactId>junit</artifactId>
 5       <version>4.12</version>
 6       <scope>test</scope>
 7     </dependency>
 8     <dependency>
 9         <groupId>org.springframework</groupId>
10         <artifactId>spring-context</artifactId>
11         <version>${spring.version}</version>
12     </dependency>
13     <dependency>
14         <groupId>org.springframework</groupId>
15         <artifactId>spring-core</artifactId>
16         <version>${spring.version}</version>
17     </dependency>
18     <dependency>
19         <groupId>org.springframework</groupId>
20         <artifactId>spring-jdbc</artifactId>
21         <version>${spring.version}</version>
22     </dependency>
23     <dependency>
24         <groupId>org.springframework</groupId>
25         <artifactId>spring-tx</artifactId>
26         <version>${spring.version}</version>
27     </dependency>
28     <dependency>
29         <groupId>commons-dbcp</groupId>
30         <artifactId>commons-dbcp</artifactId>
31         <version>1.4</version>
32     </dependency>
33     <dependency>
34         <groupId>mysql</groupId>
35         <artifactId>mysql-connector-java</artifactId>
36         <version>${mysql.version}</version>
37     </dependency>
38     <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
39     <dependency>
40         <groupId>org.mybatis</groupId>
41         <artifactId>mybatis</artifactId>
42         <version>3.4.4</version>
43     </dependency>
44   </dependencies>
pom.xml中的dependencies
2.Spring配置
(1)配置db.properties
修改:
1.url修改为正斜杠,student加s
2.设置mysql密码后,在文件中也写上密码
3.url添加参数,设置字符集,禁用SSL。
1 driver=com.mysql.jdbc.Driver
2 url=jdbc:mysql://localhost:3306/students?useUnicode=true&characterEncoding=UTF-8&useSSL=false
3 username=root
4 password=pass
5 initialSize=3
6 maxActive=10
db.properties
(2)配置applicationContext.xml
修改:修改读取db.properties的方式。
说明:用这种方式可以读取多个外部属性文件,完整的格式为:
1 <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
2     <property name="ignoreUnresolvablePlaceholders" value="true"/>
3     <property name="location">
4         <list>
5             <value>classpath:db.properties</value>
6             <value>classpath:jdbc.properties</value>
7         </list>
8     </property>
9 </bean>

applicationContext.xml:这里的头文件使用Spring Tool Suite生成的。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 6     xmlns:util="http://www.springframework.org/schema/util"
 7     xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
 8         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 9         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
10         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
11     <!--<context:property-placeholder location="classpath:db.properties"/>-->
12     <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
13         <property name="location" value="classpath:db.properties"></property>
14     </bean>
15     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
16         <property name="username" value="${username}"></property>
17         <property name="password" value="${password}"></property>
18         <property name="url" value="${url}"></property>
19         <property name="driverClassName" value="${driver}"></property>
20         <property name="initialSize" value="${initialSize}"></property>
21         <property name="maxActive" value="${maxActive}"></property>
22     </bean>
23     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
24     <!--<bean id="jdbcTemplate" class="cn.cage.student.StudentDAOImpl">-->
25         <property name="dataSource" ref="dataSource"></property>
26     </bean>
27 </beans>
applicationContext.xml

3.创建Student类,用于描述学生

修改:

 * 1.添加hashCode和equals方法
 * 2.将构造方法获取的参数由int改为Interger,否则在Mybatis查询中,会出现“找不到类型为[String,String,Integer]的构造函数”的报错。

 

  1 /**
  2  * @FileName:Student.java
  3  * @description:
  4  * @author Cage Yang
  5  * @version 
  6  * Modified Date:2017年7月27日
  7  * Why & What is modified: 
  8  * 1.添加hashCode和equals方法
  9  * 2.将构造方法获取的参数由int改为Interger,否则在Mybatis查询中,会出现“找不到类型为[String,String,Integer]的构造函数”的报错。
 10  */
 11 package cn.cage.student;
 12 
 13 /**
 14  * @ClassName Student
 15  * @description 描述学生信息的类。
 16  * @author Cage Yang 
 17  */
 18 public class Student implements Comparable<Student> {
 19     private long id;
 20     private long createTime;
 21     private long updateTime;
 22     private String name;
 23     private String qq;
 24     private String major;    //学习方向
 25     private String entryTime;
 26     private String school;
 27     private int jnshuId;    //修真院ID
 28     private String dailyUrl;
 29     private String desire;
 30     private String jnshuBro;//修真院师兄
 31     private String knowFrom;//信息来源
 32     /**
 33      * @param name 学生姓名
 34      * @param major 学习方向
 35      * @param jnshuId 修真院ID
 36      */
 37     public Student(String name, String major, Integer jnshuId) {
 38         this.name = name;
 39         this.major = major;
 40         this.jnshuId = jnshuId;
 41     }
 42     /* (非 Javadoc)
 43      * @see java.lang.Object#toString()
 44      */
 45     @Override
 46     public String toString() {
 47         return "Student [name=" + name + ", major=" + major + ", jnshuId=" + jnshuId + "]";
 48     }
 49     /* (非 Javadoc)
 50      * @see java.lang.Object#hashCode()
 51      */
 52     @Override
 53     public int hashCode() {
 54         final int prime = 31;
 55         int result = 1;
 56         result = prime * result + jnshuId;
 57         result = prime * result + ((major == null) ? 0 : major.hashCode());
 58         return result;
 59     }
 60     /* (非 Javadoc)
 61      * @see java.lang.Object#equals(java.lang.Object)
 62      */
 63     @Override
 64     public boolean equals(Object obj) {
 65         if (this == obj) {
 66             return true;
 67         }
 68         if (obj == null) {
 69             return false;
 70         }
 71         if (!(obj instanceof Student)) {
 72             return false;
 73         }
 74         Student other = (Student) obj;
 75         if (jnshuId != other.jnshuId) {
 76             return false;
 77         }
 78         if (major == null) {
 79             if (other.major != null) {
 80                 return false;
 81             }
 82         } else if (!major.equals(other.major)) {
 83             return false;
 84         }
 85         return true;
 86     }
 87     /* (非 Javadoc)
 88      * @see java.lang.Comparable#compareTo(java.lang.Object)
 89      */
 90     public int compareTo(Student o) {
 91         // TODO 自动生成的方法存根
 92         if (this.id!=o.id) {    //其实相等就是指二者id都不存在(为0)的情况
 93             return (new Long(this.id)).compareTo(new Long(o.id));
 94         }
 95         if (!this.major.equals(o.major)) {
 96             return this.major.compareTo(o.major);
 97         }
 98         return (new Integer(this.jnshuId).compareTo(new Integer(o.jnshuId)));
 99     }
100     /**
101      * @return id
102      */
103     public long getId() {
104         return id;
105     }
106     /**
107      * @param id 要设置的 id
108      */
109     public void setId(long id) {
110         this.id = id;
111     }
112     /**
113      * @return name
114      */
115     public String getName() {
116         return name;
117     }
118     /**
119      * @param name 要设置的 name
120      */
121     public void setName(String name) {
122         this.name = name;
123     }
124     /**
125      * @return qq
126      */
127     public String getQq() {
128         return qq;
129     }
130     /**
131      * @param qq 要设置的 qq
132      */
133     public void setQq(String qq) {
134         this.qq = qq;
135     }
136     /**
137      * @return major
138      */
139     public String getMajor() {
140         return major;
141     }
142     /**
143      * @param major 要设置的 major
144      */
145     public void setMajor(String major) {
146         this.major = major;
147     }
148     /**
149      * @return entryTime
150      */
151     public String getEntryTime() {
152         return entryTime;
153     }
154     /**
155      * @param entryTime 要设置的 entryTime
156      */
157     public void setEntryTime(String entryTime) {
158         this.entryTime = entryTime;
159     }
160     /**
161      * @return school
162      */
163     public String getSchool() {
164         return school;
165     }
166     /**
167      * @param school 要设置的 school
168      */
169     public void setSchool(String school) {
170         this.school = school;
171     }
172     /**
173      * @return jnshuId
174      */
175     public int getJnshuId() {
176         return jnshuId;
177     }
178     /**
179      * @param jnshuId 要设置的 jnshuId
180      */
181     public void setJnshuId(int jnshuId) {
182         this.jnshuId = jnshuId;
183     }
184     /**
185      * @return dailyUrl
186      */
187     public String getDailyUrl() {
188         return dailyUrl;
189     }
190     /**
191      * @param dailyUrl 要设置的 dailyUrl
192      */
193     public void setDailyUrl(String dailyUrl) {
194         this.dailyUrl = dailyUrl;
195     }
196     /**
197      * @return desire
198      */
199     public String getDesire() {
200         return desire;
201     }
202     /**
203      * @param desire 要设置的 desire
204      */
205     public void setDesire(String desire) {
206         this.desire = desire;
207     }
208     /**
209      * @return jnshuBro
210      */
211     public String getJnshuBro() {
212         return jnshuBro;
213     }
214     /**
215      * @param jnshuBro 要设置的 jnshuBro
216      */
217     public void setJnshuBro(String jnshuBro) {
218         this.jnshuBro = jnshuBro;
219     }
220     /**
221      * @return knowFrom
222      */
223     public String getKnowFrom() {
224         return knowFrom;
225     }
226     /**
227      * @param knowFrom 要设置的 knowFrom
228      */
229     public void setKnowFrom(String knowFrom) {
230         this.knowFrom = knowFrom;
231     }
232     /**
233      * @return createTime
234      */
235     public long getCreateTime() {
236         return createTime;
237     }
238     /**
239      * @return updateTime
240      */
241     public long getUpdateTime() {
242         return updateTime;
243     }
244     /**
245      * @param createTime 要设置的 createTime
246      */
247     public void setCreateTime(long createTime) {
248         this.createTime = createTime;
249     }
250     /**
251      * @param updateTime 要设置的 updateTime
252      */
253     public void setUpdateTime(long updateTime) {
254         this.updateTime = updateTime;
255     }
256     
257     
258 }
POJO类:Student

 

4.创建数据传递接口,规定操作数据的方法

 * 1.将updateStu函数的返回值由Student修改为boolean,参数列表增加long id。
 * 原因:不需要获取旧学生信息;由调用者提供id参数更好。
 * 2.修改queryStuByName的返回值为List<Student>,因为可能出现的同名学生。
 * 3.修改delStuByName的返回值为int,因为可能出现的同名学生。
 * 4.删除delStuByName和delStuByJnshu两个方法。
 *  原因:在实际业务逻辑中一般是先查询,然后根据查询结果选择删除。既然是这样,只需要根据选择项的id删除即可,不会出现误删的问题。
 1 /**
 2  * @FileName:DataOperate.java
 3  * @description:
 4  * @author Cage Yang
 5  * @version 
 6  * Modified Date:2017年8月4日
 7  * Why & What is modified: 
 8  * 1.将updateStu函数的返回值由Student修改为boolean,参数列表增加long id。
 9  *         原因:不需要获取旧学生信息;由调用者提供id参数更好。
10  * 2.修改queryStuByName的返回值为List<Student>,因为可能出现的同名学生。
11  * 3.修改delStuByName的返回值为int,因为可能出现的同名学生。
12  * 4.删除delStuByName和delStuByJnshu两个方法。
13  *      原因:在实际业务逻辑中一般是先查询,然后根据查询结果选择删除。既然是这样,只需要根据选择项的id删除即可,不会出现误删的问题。
14 
15  */
16 package cn.cage.student;
17 
18 import java.util.List;
19 
20 /**
21  * @ClassName DataOperate
22  * @description 数据操作接口,规定了对学生对象数据的增删改查四种操作。
23  * @author Cage Yang 
24  */
25 public interface StudentDAO {
26     //
27     public abstract boolean addStu(Student stu);
28     
29     //
30     public abstract boolean delStuById(long id);
31 //    public abstract int delStuByName(String name);
32 //    public abstract boolean delStuByJnshu(String major, int jnshuId);
33     public abstract boolean delStu(Student stu);
34     
35     //
36     public abstract boolean updateStu(Student stu, long id);
37     
38     //
39     public abstract Student queryStuById(long id);
40     public abstract List<Student> queryStuByName(String name);
41     public abstract Student queryStuByJnshu(String major, int jnshuId);
42     public abstract Student queryStu(Student stu);
43 }
DAO接口

5.创建数据传递类,实现数据操作

修改:

 * 1.将updateStu函数的返回值由Student修改为boolean,参数列表增加long id。
 *  函数体:将返回值设为“影响行数>0则返回true”;sql语句中的条件参数由stu.getId()改为id。
 * 原因:不需要获取旧学生信息;由调用者提供id参数更好。
 * 2.在addStu函数中,去掉sql语句中id的添加,id由数据库自增完成,以免发生id重复的情况。
 * 3.将各查询方法中的RowMapper提取出来封装为一个类,在各查询方法中直接使用此类对象,避免重复代码。
 *  为适配封装的QueryStuRowMapper,将各查询方法中的sql语句改为查询所有属性。
 * 4.修改addStu函数体,当name、major、jnshuId为空时报错。
 * 5.在updateStu函数体中也加上非空报错。
 * 6.bug:各sql语句中的表名是student,数据库表名是students,应修改。
 * 7.在通过id、专业+学号查询的方法中,通过try-catch捕获EmptyResultDataAccessException异常,给出查询结果为空的提示。
 * 8.修改queryStuByName的返回值为List<Student>,因为可能出现的同名学生;然后通过query函数查询,且当结果size==0时,给出查询结果为空的提示。
 * 9.修改delStuByName的返回值为int,因为可能出现的同名学生。
 * 10.删除delStuByName和delStuByJnshu两个方法。

 * 原因:在实际业务逻辑中一般是先查询,然后根据查询结果选择删除。既然是这样,只需要根据选择项的id删除即可,不会出现误删的问题。

  1 /**
  2  * @FileName:StudentDAOSpringImpl.java
  3  * @description:
  4  * @author Cage Yang
  5  * @version 
  6  * Modified Date:2017年8月4日
  7  * Why & What is modified:
  8  * 1.将updateStu函数的返回值由Student修改为boolean,参数列表增加long id。
  9  *       函数体:将返回值设为“影响行数>0则返回true”;sql语句中的条件参数由stu.getId()改为id。
 10  *         原因:不需要获取旧学生信息;由调用者提供id参数更好。
 11  * 2.在addStu函数中,去掉sql语句中id的添加,id由数据库自增完成,以免发生id重复的情况。
 12  * 3.将各查询方法中的RowMapper提取出来封装为一个类,在各查询方法中直接使用此类对象,避免重复代码。
 13  *       为适配封装的QueryStuRowMapper,将各查询方法中的sql语句改为查询所有属性。
 14  * 4.修改addStu函数体,当name、major、jnshuId为空时报错。
 15  * 5.在updateStu函数体中也加上非空报错。
 16  * 6.bug:各sql语句中的表名是student,数据库表名是students,应修改。
 17  * 7.在通过id、专业+学号查询的方法中,通过try-catch捕获EmptyResultDataAccessException异常,给出查询结果为空的提示。
 18  * 8.修改queryStuByName的返回值为List<Student>,因为可能出现的同名学生;然后通过query函数查询,且当结果size==0时,给出查询结果为空的提示。
 19  * 9.修改delStuByName的返回值为int,因为可能出现的同名学生。
 20  * 10.删除delStuByName和delStuByJnshu两个方法。
 21  *         原因:在实际业务逻辑中一般是先查询,然后根据查询结果选择删除。既然是这样,只需要根据选择项的id删除即可,不会出现误删的问题。
 22  */
 23 package cn.cage.student;
 24 
 25 
 26 import java.util.List;
 27 
 28 import org.springframework.context.ApplicationContext;
 29 import org.springframework.context.support.ClassPathXmlApplicationContext;
 30 import org.springframework.dao.EmptyResultDataAccessException;
 31 import org.springframework.jdbc.core.JdbcTemplate;
 32 
 33 /**
 34  * @ClassName StudentDAOSpringImpl
 35  * @description 
 36  * @author Cage Yang 
 37  */
 38 public class StudentDAOSpringImpl implements StudentDAO {
 39     private ApplicationContext apc = null;
 40     private JdbcTemplate jdbcTemplate = null;
 41 
 42     /**
 43      * 创建对象的同时获取jdbcTemplate对象。
 44      */
 45     public StudentDAOSpringImpl() {
 46         // TODO 自动生成的构造函数存根
 47         apc = new ClassPathXmlApplicationContext("applicationContext.xml");
 48         jdbcTemplate = (JdbcTemplate) apc.getBean("jdbcTemplate");
 49     }
 50     /* (非 Javadoc)
 51      * @see cn.cage.student.StudentDAO#addStu(cn.cage.student.Student)
 52      */
 53     public boolean addStu(Student stu) {
 54         // TODO 自动生成的方法存根
 55         String name = stu.getName(), major = stu.getMajor();
 56         int jnshuId = stu.getJnshuId();
 57         if (name==null || major==null || jnshuId==0) {
 58             throw new RuntimeException("addStu:姓名、专业、学号不能为空!");
 59         }
 60         String sql = "INSERT INTO students (name,qq,major,entrytime,gra_school,id_jnshu"
 61                 + ",daily_url,desire,bro_jnshu,knowfrom) VALUES (?,?,?,?,?,?,?,?,?,?)";
 62         int line = jdbcTemplate.update(sql,new Object[] {
 63                 name,stu.getQq(),major,stu.getEntryTime(),
 64                 stu.getSchool(),jnshuId,stu.getDailyUrl(),stu.getDesire(),
 65                 stu.getJnshuBro(),stu.getKnowFrom()});
 66         return line>0?true:false;
 67     }
 68 
 69     /* (非 Javadoc)
 70      * @see cn.cage.student.StudentDAO#delStuById(long)
 71      */
 72     public boolean delStuById(long id) {
 73         // TODO 自动生成的方法存根
 74         String sql = "DELETE FROM students WHERE id=?";
 75         int line = jdbcTemplate.update(sql,id);
 76         return line>0?true:false;
 77     }
 78 
 79     /* (非 Javadoc)
 80      * @see cn.cage.student.StudentDAO#delStuByName(java.lang.String)
 81      */
 82     /*
 83     public int delStuByName(String name) {
 84         // TODO 自动生成的方法存根
 85         String sql = "DELETE FROM students WHERE name=?";
 86         return jdbcTemplate.update(sql,name);
 87     }
 88      */
 89     /* (非 Javadoc)
 90      * @see cn.cage.student.StudentDAO#delStuByJnshu(java.lang.String, int)
 91      */
 92     /*
 93     public boolean delStuByJnshu(String major, int jnshuId) {
 94         // TODO 自动生成的方法存根
 95         String sql = "DELETE FROM students WHERE id_jnshu=? and major=?";
 96         int line = jdbcTemplate.update(sql,new Object[] {jnshuId,major});
 97         return line>0?true:false;
 98     }
 99      */
100     /* (非 Javadoc)
101      * 用于日后需要添加通过其他元素删除学生记录时的情况
102      * @see cn.cage.student.StudentDAO#delStu(cn.cage.student.Student)
103      */
104     public boolean delStu(Student stu) {
105         // TODO 自动生成的方法存根
106         return false;
107     }
108 
109     /* (非 Javadoc)
110      * @see cn.cage.student.StudentDAO#updateStu(cn.cage.student.Student)
111      */
112     public boolean updateStu(Student stu, long id) {
113         // TODO 自动生成的方法存根
114         String name = stu.getName(), major = stu.getMajor();
115         int jnshuId = stu.getJnshuId();
116         if (name==null || major==null || jnshuId==0) {
117             throw new RuntimeException("updateStu:姓名、专业、学号不能为空!");
118         }
119         String sql = "UPDATE students SET name=?,qq=?,major=?,entrytime=?,gra_school=?,id_jnshu=?"
120                 + ",daily_url=?,desire=?,bro_jnshu=?,knowfrom=? WHERE id=?";
121         int line = jdbcTemplate.update(sql,new Object[]{
122                 name,stu.getQq(),major,stu.getEntryTime(),
123                 stu.getSchool(),jnshuId,stu.getDailyUrl(),stu.getDesire(),
124                 stu.getJnshuBro(),stu.getKnowFrom(),id});
125         return line>0?true:false;
126     }
127 
128     /* (非 Javadoc)
129      * @see cn.cage.student.StudentDAO#queryStuById(int)
130      */
131     public Student queryStuById(long id) {
132         // TODO 自动生成的方法存根
133         String sql = "SELECT id,create_at,update_at,name,qq,major,entrytime,gra_school" + 
134                 ",id_jnshu,daily_url,desire,bro_jnshu,knowfrom FROM students WHERE id=?";
135         Student stu = null;
136         try {
137             stu = jdbcTemplate.queryForObject(sql, new QueryStuRowMapper(),id);
138         } catch (EmptyResultDataAccessException e) {
139             // TODO 此处可做更复杂的提示动作,比如抛出异常、记录到本地文件、显示到GUI等。
140             System.out.println("queryStuById:该学生不存在!");
141         }
142         return stu;
143     }
144 
145     /* (非 Javadoc)
146      * @see cn.cage.student.StudentDAO#queryStuByName(java.lang.String)
147      */
148     public List<Student> queryStuByName(String name) {
149         // TODO 自动生成的方法存根
150         String sql = "SELECT id,create_at,update_at,name,qq,major,entrytime,gra_school" + 
151                 ",id_jnshu,daily_url,desire,bro_jnshu,knowfrom FROM students WHERE name=?";
152         List<Student> list = jdbcTemplate.query(sql, new QueryStuRowMapper(),name);
153         if (list.size()==0) {
154             System.out.println("queryStuByName:该学生不存在!");
155         }
156         return list;
157     }
158 
159     /* (非 Javadoc)
160      * @see cn.cage.student.StudentDAO#queryStuByJnshu(java.lang.String, int)
161      */
162     public Student queryStuByJnshu(String major, int jnshuId) {
163         // TODO 自动生成的方法存根
164         String sql = "SELECT id,create_at,update_at,name,qq,major,entrytime,gra_school,id_jnshu" + 
165                 ",daily_url,desire,bro_jnshu,knowfrom FROM students WHERE id_jnshu=? and major=?";
166         Student stu = null;
167         try {
168             stu = jdbcTemplate.queryForObject(sql, new QueryStuRowMapper(),new Object[]{jnshuId,major});
169         } catch (EmptyResultDataAccessException e) {
170             // TODO 此处可做更复杂的提示动作,比如抛出异常、记录到本地文件、显示到GUI等。
171             System.out.println("queryStuByJnshu:该学生不存在!");
172         }
173         return stu;
174     }
175 
176     /* (非 Javadoc)
177      * 用于日后需要添加通过其他元素查询学生记录时的情况
178      * @see cn.cage.student.StudentDAO#queryStu(cn.cage.student.Student)
179      */
180     public Student queryStu(Student stu) {
181         // TODO 自动生成的方法存根
182         return null;
183     }
184 
185 }
DAO接口的实现类

提取出的RowMapper实现类:

注:从MySQL中获取的Date值,在java中是java.sql.Date,toString的格式为YYYY-MM-dd。

修改:

 * 1.给entrytime的设定语句加上非空判断,否则当其为空时,null.toString抛出空指针异常。

 1 /**
 2  * @FileName:QueryStuRowMapper.java
 3  * @description:
 4  * @author Cage Yang
 5  * @version 
 6  * Modified Date:2017年8月4日
 7  * Why & What is modified: 
 8  * 1.给entrytime的设定语句加上非空判断,否则当其为空时,null.toString抛出空指针异常。
 9  */
10 package cn.cage.student;
11 
12 import java.sql.Date;
13 import java.sql.ResultSet;
14 import java.sql.SQLException;
15 
16 import org.springframework.jdbc.core.RowMapper;
17 
18 /**
19  * @ClassName QueryStuRowMapper
20  * @description 直接使用此类建立的对象时,sql语句应查询Student的所有属性。
21  * @author Cage Yang 
22  */
23 public class QueryStuRowMapper implements RowMapper<Student> {
24     /**
25      * 直接使用此类建立的对象时,sql语句应查询Student的所有属性。
26      * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
27      */
28     public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
29         // TODO 自动生成的方法存根
30         Student stu = new Student(rs.getString("name"), rs.getString("major"), rs.getInt("id_jnshu"));
31         stu.setId(rs.getLong("id"));
32         stu.setCreateTime(rs.getLong("create_at"));
33         stu.setUpdateTime(rs.getLong("update_at"));
34         stu.setQq(rs.getString("qq"));
35         // TODO 从Mysql中获取的Date值,在java中是java.sql.Date,toString的格式为YYYY-MM-dd。
36         Date entryTime = rs.getDate("entrytime");
37         if (entryTime!=null) {
38             stu.setEntryTime(entryTime.toString());
39         }
40         stu.setSchool(rs.getString("gra_school"));
41         stu.setDailyUrl(rs.getString("daily_url"));
42         stu.setDesire(rs.getString("desire"));
43         stu.setJnshuBro(rs.getString("bro_jnshu"));
44         stu.setKnowFrom(rs.getString("knowfrom"));
45         return stu;
46     }
47 }
查询方法的RowMapper

6.创建DAO工厂类,提供Impl对象

 1 /**
 2  * @FileName:StudentDAO.java
 3  * @description:
 4  * @author Cage Yang
 5  * @version 
 6  * Modified Date:2017年7月27日
 7  * Why & What is modified: <修改原因描述>
 8  */
 9 package cn.cage.student;
10 
11 /**
12  * @ClassName StudentDAO
13  * @description DAO工厂类,用于创建数据操作对象。
14  * @author Cage Yang 
15  */
16 public class DAOFactory {
17     public static StudentDAO getDAOImpl() {
18         return new StudentDAOSpringImpl();
19     }
20 }
DAO工厂

7.测试类

(1)原始测试,bug修改与代码完善
在未学习JUnit的情况下,在main/java中写了个简单的功能测试代码,来测试数据操作类,出现以下bug:
 
①bug修改
bug1:url错误。
错误原因:a.数据库名是students,少了个s;b.是正斜杠而不是反斜杠
bug2:Access denied for user 'asus'@'localhost' (using password: YES)
错误定位:db.properties读取(<context:property-placeholder location="classpath:db.properties"/>)发生错误。
错误原因:未明。
修改:将读取db.properties的方式修改为:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
        <property name="location" value="classpath:db.properties"></property>
</bean>
即可正常读取。
bug3:Table 'students.student' doesn't exist
错误原因:各sql语句中插入的表名是student,应该为students
bug4:Column 'jnshuId' not found.
错误原因:mapRow中的rs.getString("")引号中应为数据库的列名,而不是Student类中的属性名 。
bug5:打印结果为数组地址
修改:将showStu函数中的sop(obj)改为foreach循环遍历。
Warn:Sat Aug 05 03:09:59 CST 2017 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
原因:未设置数据库的连接属性(是否使用SSL)。
解决:将url修改为:jdbc:mysql://localhost:3306/students?useUnicode=true&characterEncoding=UTF-8&useSSL=false,即可。
 
②测试
修改完上述bug后,测试类可正常运行。测试出以下问题:
查询方法的测试:
>通过id查询
查询不存在的id:
Student stu = test.stuDAO.queryStuByName("胡凯博");
test.showStu(stu);
结果:
处理方式:程序中应该添加对各查询函数查询的学生不存在的问题的处理。
>通过name查询:
Student stu = test.stuDAO.queryStuByName("胡凯博");
test.showStu(stu);

结果:


原因:返回了大量同名的学生。
处理方式:所以,通过姓名查询时,应该返回List<Student>而不是Student。
 
③代码完善
根据测试结果,需要修改的地方有:
  • 各查询函数中,添加对要查询的学生不存在的情况的处理
  • 通过name查询学生时,返回值改为List<Student>,因为可能有重名学生
  • 通过name删除学生时,返回值改为int,因为可能删除多名学生
通过查看源码,发现queryForObject必须返回且仅返回一个对象,查询时调用query方法,获取List<T>。List为空时抛出EmptyResultDataAccessException异常,List元素多于1个时抛出IncorrectResultSizeDataAccessException异常。

所以,在通过id、专业+学号查询时,通过try-catch捕获异常,给出查询结果为空的提示;通过name查询时,改为调用query方法。

 

(2)JUnit单元测试
学习JUnit后,编写JUnit测试类对数据操作类进行单元测试。
关于JUnit的基础知识,详见:JUnit4在Eclipse中的使用
①测试类的编写:
 1 /**
 2  * @FileName:StuDaoImplTest.java
 3  * @description:
 4  * @author Cage Yang
 5  * @version 
 6  * Modified Date:2017年8月8日
 7  * Why & What is modified: <修改原因描述>
 8  */
 9 package cn.cage.student;
10 
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertNull;
13 import static org.junit.Assert.assertTrue;
14 
15 import java.util.Iterator;
16 import java.util.List;
17 
18 import org.junit.AfterClass;
19 import org.junit.BeforeClass;
20 import org.junit.Test;
21 
22 
23 /**
24  * @ClassName StuDaoImplTest
25  * @description 
26  * @author Cage Yang 
27  */
28 public class StuDaoImplTest {
29     private static StudentDAOSpringImpl stuDao = null;
30     /**
31      * @description 
32      * @throws java.lang.Exception 
33      */
34     @BeforeClass
35     public static void setUpBeforeClass() throws Exception {
36         stuDao = new StudentDAOSpringImpl();
37     }
38     
39     @AfterClass
40     public static void tearDownAfterClass() throws Exception {
41         stuDao = null;
42     }
43 
44     /**
45      * {@link cn.cage.student.StudentDAOSpringImpl#addStu(cn.cage.student.Student)} 的测试方法。
46      */
47     @Test
48     public void testAddStu() {
49         Student stu = RandomStudent.getStudent();
50         stu.setEntryTime("1995-8-6");
51         assertTrue("插入失败!",stuDao.addStu(stu));
52         assertEquals("插入错误,或查询byJnshu出错", stu, stuDao.queryStuByJnshu("Java后端工程师", 1501));
53     }
54 
55     /**
56      * {@link cn.cage.student.StudentDAOSpringImpl#delStuById(long)} 的测试方法。
57      */
58     @Test
59     public void testDelStuById() {
60         assertTrue("删除失败!",stuDao.delStuById(3));
61         assertNull("删除错误,或查询byId出错", stuDao.queryStuById(3));
62     }
63 
64     /**
65      * {@link cn.cage.student.StudentDAOSpringImpl#updateStu(cn.cage.student.Student, long)} 的测试方法。
66      */
67     @Test
68     public void testUpdateStu() {
69         Student stu = RandomStudent.getStudent();
70         stu.setDesire("哈哈哈哈哈哈哈哈");
71         assertTrue("更新失败!", stuDao.updateStu(stu, 2));
72         assertEquals("更新错误,或查询byId出错", "哈哈哈哈哈哈哈哈", stuDao.queryStuById(2).getDesire());
73     }
74 
75     /**
76      * {@link cn.cage.student.StudentDAOSpringImpl#queryStuByName(java.lang.String)} 的测试方法。
77      */
78     @Test
79     public void testQueryStuByName() {
80         List<Student> list = stuDao.queryStuByName("王五");
81         for (Iterator<Student> iterator = list.iterator(); iterator.hasNext();) {
82             Student student = (Student) iterator.next();
83             if (student.getJnshuId()==1509) {
84                 assertEquals("查询byName出错", "2017-08-06", student.getEntryTime());
85             }
86         }
87     }
88 }
JUnit4单元测试

②测试结果

>2个运行时发生错误的,经过检查,是因为插入的记录没有entrytime(java.sql.Date类型)的值,运行查询方法时,mapRow方法中执行stu.setEntryTime(rs.getDate("entrytime").toString());时,rs.getDate("entrytime")结果是null,null.toString()会抛出空指针异常。
修改:给mapRow中的这句话加上if判断语句,仅当非空时执行相关操作。
>1个运行结果与预测不符的,经过检查,是因为Student类中没有定义equals方法,于是assertEquals实际上是在判断二者是否引用同一对象。
修改:在Student类中添加equals函数,当专业、学号相同时判定为同一学生。
做完上述修改后,再进行测试,通过。
 
四、小结
在学习这个项目中的收获:
  1. 即使不存在安全性问题,MySQL也应该设定密码。否则可能出现程序无法连接到数据库的情况。
  2. POJO类中,尽量做到各属性名称、数据类型都和数据库中相同,这样可以免去很多麻烦。
  3. POJO类都要自定义toString、hashCode、equals、compareTo方法。特别是前三者,比较两个实例是否相同是一个很常见的操作,如果不自定义,根本无法比较。compareTo是考虑到实例可能存入TreeSet,最好自定义;不过即使不自定义,也可通过比较器来实现比较功能。
  4. 对于DAO接口及其实现类,增删改最好返回int型(影响行数);在insert、update数据时,要考虑到数据库设计时加了非空约束的列,对应属性为空值时直接抛出异常,不向数据库提交数据;delete方法一般只需要一个(比如id)即可,因为正常逻辑是先查询到要删除的数据,在根据查询结果中对应的id删除即可;在select数据时,要考虑到查询值不存在、查询值不唯一的情况,做出应对,避免抛出意料外的异常。
  5. 学习了JUnit4单元测试的编写与使用。
posted @ 2017-08-04 02:46  花火·  阅读(380)  评论(0编辑  收藏  举报