接着前面那个demo讲,之前我们没有引入数据库,这里我们加入数据库MySQL。(工程day14_user
)
一、建立数据库
CREATE TABLE `users` (
`id` varchar(40) NOT NULL,
`username` varchar(40) NOT NULL,
`password` varchar(40) NOT NULL,
`email` varchar(100) DEFAULT NULL,
`birthday` date DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
二、改造dao层
之前我们dao层实现是使用的XML充当的数据库,于是操作的是XML文档,这里我们需要将dao层实现换成操作数据库。
UserDaoJdbcImpl.java
package cn.itcast.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
import cn.itcast.utils.JdbcUtils;
import cn.itcast.utils.XmlUtils;
public class UserDaoJdbcImpl implements UserDao {
//使用用户名和密码去查找,用于用户登录
@Override
public User find(String username , String password) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet result = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select * from users where username=? and password=?";
ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, password);
result = ps.executeQuery();
if(result.next()){
User user = new User();
user.setId(result.getString("id"));
user.setUsername(result.getString("username"));
user.setPassword(result.getString("password"));
user.setEmail(result.getString("email"));
user.setBirthday(result.getDate("birthday"));
return user;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.release(conn, ps, result);
}
return null;
}
//使用用户名去查找,用于用户注册,查看系统中此用户名是否被使用
@Override
public User find(String username){
Connection conn = null;
PreparedStatement ps = null;
ResultSet result = null;
try{
conn = JdbcUtils.getConnection();
String sql = "select * from users where username=?";
ps = conn.prepareStatement(sql);
ps.setString(1, username);
result = ps.executeQuery();
if(result.next()){
User user = new User();
user.setId(result.getString("id"));
user.setUsername(result.getString("username"));
user.setPassword(result.getString("password"));
user.setEmail(result.getString("email"));
user.setBirthday(result.getDate("birthday"));
return user;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.release(conn, ps, result);
}
return null;
}
//添加用户
@Override
public void add(User user){
Connection conn = null;
PreparedStatement ps = null;
ResultSet result = null;
try{
conn = JdbcUtils.getConnection();
String sql = "insert into users(id, username, password, email, birthday) values(?,?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1, user.getId());
ps.setString(2, user.getUsername());
ps.setString(3, user.getPassword());
ps.setString(4, user.getEmail());
//设置日期注意java中和数据库中是不同的
ps.setDate(5, new java.sql.Date(user.getBirthday().getTime()));
ps.executeUpdate();
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtils.release(conn, ps, result);
}
}
}
说明:这里我们不再使用Statement这个类了,这个类使用起来较为麻烦,我们使用其子类PreparedStatement,使用较为简单,这里就不多说了。
JdbcUtils.java
package cn.itcast.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcUtils {
private static String driver;
private static String url ;
private static String username;
private static String password;
static {
try{
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
Class.forName(driver);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection(url, username, password);
}
public static void release(Connection conn, PreparedStatement ps, ResultSet result){
if(result != null){
try {
result.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(ps != null){
try {
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
三、改造service层
在之前的service层中我们使用的是UserDaoXmlImpl.java
,这里我们使用UserDaoJdbcImpl.java
,于是只需要将
UserDao dao = new UserDaoXmlImpl();
换成
UserDao dao = new UserDaoJdbcImpl();
即可,但是假如以后我们不想更改service层,则可以使用工厂设计模式,让工厂帮我们实现好一个UserDao,我们直接拿过来用即可,如:
UserDao dao = DaoFactory.getInstance().createUserDao();
工厂设计模式其实就是去读取配置文件,根据配置文件来实现哪个dao实现,这里给出配置文件:
dao.properties
#userdao=cn.itcast.dao.impl.UserDaoXmlImpl
userdao=cn.itcast.dao.impl.UserDaoJdbcImpl
工厂方法(DaoFactory.java
)
package cn.itcast.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itcast.dao.UserDao;
public class DaoFactory {
//工厂设计模式一般是单例的
private static UserDao userDao = null ;
private DaoFactory(){
//我们一般将只执行一次的代码放在静态代码块中,但是这里的工厂是单例的,所以我们还可以
//将这样的代码放在其构造函数中
InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
Properties properties = new Properties();
try {
properties.load(in);
String daoClassName = properties.getProperty("userdao");
userDao = (UserDao) Class.forName(daoClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static final DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance(){
return instance;
}
public UserDao createUserDao(){
return userDao;
}
}
这样我们在以后如果想跟换数据库就只需要改一下配置文件即可。
最后:
上面我们加入数据库之后我们是使用工厂设计模式帮我们new一个UserDaoImpl,用于操作数据库,那同样我们以后service层也可能变化那我们同样可以通过工厂设计模式让工厂帮我们实现一个业务类让我们的servlet使用,其实在以后使用spring之后会发现,其实spring就是做这样的工作,它会帮我们实现好业务类和dao实现让我们使用,我们如果要用直接找spring拿就好了,spring会帮我们注入进来。而struts的功能就是实现web层的解耦,用户的请求过来之后去找struts的action,然后action去调用相关的业务类,而相关的业务类实现让spring注入进来,这就是struts的功能。而Hibernate的功能就是操作数据库。这样我们对三大框架的理解就容易多了。
补充:防sql注入。
如果我们使用的是Statement
对象,在登录时在用户名处填写'or 1=1 or username='
,则会登录成功,这是应为将这条字符串发送到服务器后被组装成了下面这条sql语句:
select * from users where username='' or 1=1 or username='' and password=''
这条语句就相当于select * from where true;
这样会以第一个用户登录成功。解决办法是使用预编译对象PreparedStatement
。这个对象是Statement
的子对象,它的实例对象可以通过调用Connection.preparedStatement()
方法获得,相对于Statement
对象而言,PreparedStatement
可以避免sql注入的问题。因为这调语句已经预编译了,之后传进来的字符串预编译之后则不会造成之前的那种情况,这样就预防了注入攻击。同时,预编译之后数据库就不需要再次编译,可以直接执行,这样就减轻了数据库的压力,提升了性能。以后都使用此对象。