还记得我前面所写的博文基于Servlet+JSP+JavaBean开发模式的用户登录注册吗?我们以前是创建代表数据库的xml文件来保存用户信息的,现在我们已经学习了数据库相关的知识,所以应把xml换成数据库,升级成数据库应用。
我们在把以前的工程复制并拷贝时,假设以前的工程名是day09_user,现复制一份并拷贝,重新修改工程名为day14_user,此刻将其直接部署在tomcat服务器上,那么day14_user这个JavaWeb应用映射的虚拟目录仍然是”/day09_user”,并不是映射成为一个同名的虚拟目录”/day14_user”,这是一个经常被人忽略的问题,要解决这个问题,可像下面这样做:
升级成数据库应用
- 导入数据库驱动
-
为应用创建相应的库和表
create database day14_user;
use day14_user;
create table users
(
id varchar(40) primary key,
username varchar(40) not null unique,
password varchar(40) not null,
email varchar(100) not null unique,
birthday date,
nickname varchar(40) not null
);
-
在src目录下创建一个db.properties文件,如下图所示:
在db.properties中编写MySQL数据库的连接信息,代码如下所示:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:
username=root
password=yezi
#driver=oracle.jdbc.driver.OracleDriver
#url=jdbc:oracle:thin:@localhost:1521:orcl
#username=system
#password=111
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
重写UserDao
现在我们要重写UserDao接口中所有的方法,在cn.itcast.dao.impl包中创建一个UserDao接口的实现类——UserDaoJdbcImpl,如下所示:
由于一开始我们写的并不是最终的代码,所以我们将重点关注public User find(String username, String password)
登录方法。
我们首先使用statement对象重写public User find(String username, String password)
方法,代码如下:
@Override
public User find(String username, String password) {
Connection conn = null
Statement st = null
ResultSet rs = null
try {
conn = JdbcUtils.getConnection()
st = conn.createStatement()
String sql = "select * from users where username='"+username+"' and password='"+password+"'"
rs = st.executeQuery(sql)
if (rs.next()) {
User user = new User()
user.setBirthday(rs.getDate("birthday"))
user.setEmail(rs.getString("email"))
user.setId(rs.getString("id"))
user.setNickname(rs.getString("nickname"))
user.setPassword(rs.getString("password"))
user.setUsername(rs.getString("username"))
return user
}
return null
} catch (Exception e) {
// 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛
throw new RuntimeException(e)
} finally {
JdbcUtils.release(conn, st, rs)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在java中关于对异常的处理真是太mb的烦了,现在总结一下对异常的处理原则。
在学界关于对java异常的处理吵的热火朝天,主要分为三大门派:
- java语言设计者——高司令,他设计出来的java语言有编译时异常和运行时异常,既然java语言都是他设计出来的,所以他认为编译时异常也是必须合理存在的。
- 《Thinking In Java》的作者认为java语言编译时异常就是垃圾,异常都应转为运行时异常抛出去。
- Spring的作者柔和了以上2人的观点,他就说——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理?如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。
所以,最好我们应采用Spring的作者的观点,那我们的处理方法就是:你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理?如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。
在实际开发中,最好每一个层都编写一个自定义异常,例如在Dao层(数据访问层)自定义一个异常类——DaoException。
在cn.itcast.exception包中创建异常类DaoException,如下:
DaoException类代码如下:
public class DaoException extends RuntimeException {
public DaoException() {
}
public DaoException(String message) {
super(message);
}
public DaoException(Throwable cause) {
super(cause);
}
public DaoException(String message, Throwable cause) {
super(message, cause);
}
public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
我们要自定义一个Dao异常抛出去,为什么呢?自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
接着修改UserDaoJdbcImpl类中的public User find(String username, String password)
方法为:
@Override
public User find(String username, String password) {
Connection conn = null
Statement st = null
ResultSet rs = null
try {
conn = JdbcUtils.getConnection()
st = conn.createStatement()
String sql = "select * from users where username='"+username+"' and password='"+password+"'"
rs = st.executeQuery(sql)
if (rs.next()) {
User user = new User()
user.setBirthday(rs.getDate("birthday"))
user.setEmail(rs.getString("email"))
user.setId(rs.getString("id"))
user.setNickname(rs.getString("nickname"))
user.setPassword(rs.getString("password"))
user.setUsername(rs.getString("username"))
return user
}
return null
} catch (Exception e) {
// 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛
throw new DaoException(e)
} finally {
JdbcUtils.release(conn, st, rs)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
实现service层和dao层的解耦
我们以前在开发service层(service层对web层提供所有的业务服务)时,编写UserService接口的具体实现类——UserServiceImpl时,业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦(希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式),要解耦,有两种方法:
现在我们重点关注工厂模式。这时我们要定义一个Dao工厂,新建一个cn.itcast.factory包,在包中创建一个Dao工厂——DaoFactory,如下所示:
工厂一般要设计成单例的,为什么呢?
答:工厂设计成单例的,工厂的对象在内存中只有一个,目的是希望所有的Dao都由一个工厂来生产。假设不把工厂设计成单例的,将来Dao由不同的工厂来生产,你觉得所有的Dao由一个工厂来生产好,还是由不同的工厂来生产好?答案显然是由一个工厂来生产好,将来维护起来好维护。
我们先编写DaoFactory类的代码为:
public class DaoFactory {
private Properties daoConfig = new Properties();
private DaoFactory() {}
private static DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance() {
return instance;
}
public <T> T createDao(Class<T> clazz) {
String name = clazz.getSimpleName();
DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
......
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
以上的代码好吗?显然这样做并不好,每次别人调用createDao方法,都会去读一下配置文件dao.properties,但此配置文件在整个系统里面只要读取一次就好,没必要老去读,即以下这行代码只需运行一次。
DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties")
那么这行代码可以放到静态代码块里。
现在将以上代码修改为:
public class DaoFactory {
static {
DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
}
private Properties daoConfig = new Properties();
private DaoFactory() {}
private static DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance() {
return instance;
}
public <T> T createDao(Class<T> clazz) {
String name = clazz.getSimpleName();
......
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
以上这样做并没有不好,但是对于现在而言还有一种做法,DaoFactory这个类被设计成单例的,所以其构造函数仅执行一次,所以可放到DaoFactory这个类的构造函数里面。所以最后完整的代码如下:
public class DaoFactory {
private Properties daoConfig = new Properties();
private DaoFactory() {
InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
try {
daoConfig.load(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance() {
return instance;
}
public <T> T createDao(Class<T> clazz) {
String name = clazz.getSimpleName();
String className = daoConfig.getProperty(name);
try {
T dao = (T) Class.forName(className).newInstance();
return dao;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
然后在src目录下创建一个dao.properties文件,如下图所示:
在db.properties中内容如下所示:
UserDao=cn.itcast.dao.impl.UserDaoJdbcImpl
实现service层和dao层的解耦,UserService接口的具体实现类——UserServiceImpl只须修改为:
public class BusinessServiceImpl {
private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class);
public void register(User user) throws UserExistException {
boolean b = dao.find(user.getUsername());
if(b) {
throw new UserExistException();
} else {
user.setPassword(ServiceUtils.md5(user.getPassword()));
dao.add(user);
}
}
public User login(String username, String password) {
password = ServiceUtils.md5(password);
return dao.find(username, password);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
防范sql注入攻击
SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
例如,statement存在sql注入攻击问题,假如登录用户名采用' or 1=1 or name='
。
对于防范SQL注入,可以采用PreparedStatement取代Statement。
PreparedStatement对象介绍
PreperedStatement是Statement的孩子,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:
- PreperedStatement可以避免SQL注入的问题。
- Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。
- 并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
使用PreparedStatement改写UserDaoJdbcImpl类的public User find(String username, String password)
方法。
@Override
public User find(String username, String password) {
Connection conn = null
PreparedStatement st = null
ResultSet rs = null
try {
conn = JdbcUtils.getConnection()
String sql = "select * from users where username=? and password=?"
st = conn.prepareStatement(sql)
st.setString(1, username)
st.setString(2, password)
rs = st.executeQuery()
if (rs.next()) {
User user = new User()
user.setBirthday(rs.getDate("birthday"))
user.setEmail(rs.getString("email"))
user.setId(rs.getString("id"))
user.setNickname(rs.getString("nickname"))
user.setPassword(rs.getString("password"))
user.setUsername(rs.getString("username"))
return user
}
return null
} catch (Exception e) {
// 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛
throw new DaoException(e)
} finally {
JdbcUtils.release(conn, st, rs)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
到此为止,我们就可以完整地写出UserDaoJdbcImpl类了,其完整代码如下:
public class UserDaoJdbcImpl implements UserDao {
@Override
public void add(User user) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "insert into users(id,username,password,email,birthday,nickname) values(?,?,?,?,?,?)";
st = conn.prepareStatement(sql);
st.setString(1, user.getId());
st.setString(2, user.getUsername());
st.setString(3, user.getPassword());
st.setString(4, user.getEmail());
st.setDate(5, new java.sql.Date(user.getBirthday().getTime()));
st.setString(6, user.getNickname());
int num = st.executeUpdate();
if(num < 1) {
throw new RuntimeException("注册用户失败!!!");
}
} catch (Exception e) {
throw new DaoException(e);
} finally {
JdbcUtils.release(conn, st, rs);
}
}
@Override
public User find(String username, String password) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from users where username=? and password=?";
st = conn.prepareStatement(sql);
st.setString(1, username);
st.setString(2, password);
rs = st.executeQuery();
if (rs.next()) {
User user = new User();
user.setBirthday(rs.getDate("birthday"));
user.setEmail(rs.getString("email"));
user.setId(rs.getString("id"));
user.setNickname(rs.getString("nickname"));
user.setPassword(rs.getString("password"));
user.setUsername(rs.getString("username"));
return user;
}
return null;
} catch (Exception e) {
throw new DaoException(e);
} finally {
JdbcUtils.release(conn, st, rs);
}
}
@Override
public boolean find(String username) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from users where username=?";
st = conn.prepareStatement(sql);
st.setString(1, username);
rs = st.executeQuery();
if (rs.next()) {
return true;
}
return false;
} catch (Exception e) {
throw new DaoException(e);
} finally {
JdbcUtils.release(conn, st, rs);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
面试题:Statement和PreparedStatement的区别。
答:
- PreparedStatement是Statement的孩子。
- PreparedStatement可以防止sql注入的问题。
- PreparedStatement会对sql语句进行预编译,以减轻数据库服务器的压力。
就像xxx.java
→xxx.class
→JVM执行
一样,sql语句
→编译
→数据库执行
。
开发完数据访问层,一定要对程序已编写好的部分代码进行测试,做一步,测试一步,以免整个程序完成后由于页面太多或者是代码量太大给查找错误造成更大的负担!所以,在juint.test包下创建了一个UserDaoJdbcTest类。
UserDaoJdbcTest类的具体代码如下:
public class UserDaoJdbcTest {
@Test
public void testAdd() {
User user = new User();
user.setBirthday(new Date());
user.setEmail("bb@sina.com");
user.setId("2142354354");
user.setNickname("李子");
user.setUsername("bbbb");
user.setPassword("123");
UserDao dao = new UserDaoJdbcImpl();
dao.add(user);
}
@Test
public void testFind() {
UserDao dao = new UserDaoJdbcImpl();
User user = dao.find("bbbb", "123");
System.out.println(user);
}
@Test
public void testFindByUsername() {
UserDao dao = new UserDaoJdbcImpl();
System.out.println(dao.find("bbbb"));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
经测试,没发现任何错误。同样也要对业务逻辑层已编写好的部分代码进行测试,在juint.test包下创建了一个ServiceTest类。
ServiceTest类的具体代码如下:
public class ServiceTest {
@Test
public void testRegister() {
User user = new User();
user.setBirthday(new Date());
user.setEmail("bb@sina.com");
user.setId("2142354354");
user.setNickname("李子");
user.setUsername("lizi");
user.setPassword("123");
BusinessServiceImpl service = new BusinessServiceImpl();
try {
service.register(user);
System.out.println("注册成功!!!");
} catch (UserExistException e) {
System.out.println("用户已存在");
}
}
@Test
public void testLogin() {
BusinessServiceImpl service = new BusinessServiceImpl();
User user = service.login("lizi", "123");
System.out.println(user);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
经测试,没发现任何错误。
到此,对基于Servlet+JSP+JavaBean开发模式的用户登录注册的升级改造圆满完成。