Früher haben wir XML-Dateien erstellt, die Datenbanken darstellen, um Benutzerinformationen zu speichern. Nachdem wir nun datenbankbezogene Kenntnisse erworben haben, sollten wir XML durch Datenbanken ersetzen und auf Datenbankanwendungen aktualisieren.
Als wir das vorherige Projekt kopiert und kopiert haben, nehmen wir an, dass der vorherige Projektname day09_user war. Erstellen Sie nun eine Kopie und kopieren Sie sie, benennen Sie das Projekt in day14_user um und stellen Sie es nun direkt auf dem Tomcat-Server bereit, dann das day14_user JavaWeb Das von der Anwendung zugeordnete virtuelle Verzeichnis ist immer noch „/day09_user“ und ist nicht einem virtuellen Verzeichnis mit demselben Namen „/day14_user“ zugeordnet. Dies ist ein Problem, das oft übersehen wird. Um dieses Problem zu lösen, können Sie Folgendes tun:
Datenbanktreiber importieren
Erstellen Sie entsprechende Bibliotheken und Tabellen für die Anwendung
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);
1
2
3
4
5
6
7
8
9
10
11
Erstellen Sie eine db.properties-Datei im src-Verzeichnis, wie unten gezeigt:
Schreiben Sie die Verbindungsinformationen der MySQL-Datenbank in db.properties. Der Code lautet wie folgt:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/day14_userusername=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
Jetzt müssen wir alle Methoden in der UserDao-Schnittstelle neu schreiben und eine Implementierungsklasse der UserDao-Schnittstelle – UserDaoJdbcImpl – im cn.itcast erstellen. dao.impl-Paket. Wie unten gezeigt:
Da das, was wir zu Beginn geschrieben haben, nicht der endgültige Code ist, konzentrieren wir uns auf die Anmeldemethode public User find(String username, String password)
.
Wir verwenden zunächst das Anweisungsobjekt, um die public User find(String username, String password)
-Methode neu zu schreiben:
@Override public User find(String username, String password) { Connection conn = null; Statement st = null; // PreparedStatement预防SQL注入 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); } }1234567891011121314151617181920212223242526272829
Die Ausnahmebehandlung in Java ist wirklich ärgerlich.
In der akademischen Gemeinschaft gibt es viele Streitigkeiten über die Behandlung von Java-Ausnahmen. Sie ist hauptsächlich in drei große Schulen unterteilt:
Java-Sprachdesigner – Commander Gao, der Die von ihm entworfene Java-Sprache besteht aus Ausnahmen zur Kompilierungszeit und zur Laufzeit. Da die Java-Sprache von ihm entworfen wurde, glaubt er, dass es vernünftigerweise auch Ausnahmen zur Kompilierungszeit geben muss.
Der Autor von „Thinking In Java“ glaubt, dass Ausnahmen zur Kompilierungszeit der Java-Sprache Müll sind und Ausnahmen in Laufzeitausnahmen umgewandelt und verworfen werden sollten.
Der Autor von Spring milderte die Ansichten der beiden oben genannten Personen. Er sagte: „Sie haben ein Programm mit Ausnahmen, was sollten Sie tun?“ Es kommt nur darauf an, ob das übergeordnete Programm damit umgehen kann? Wenn es nicht behandelt werden kann, wird es in eine Laufzeitausnahme umgewandelt und ausgelöst. Wenn es behandelt werden kann, wird es in eine Ausnahme zur Kompilierungszeit umgewandelt und direkt ausgelöst.
Es ist also am besten, wenn wir den Standpunkt des Spring-Autors übernehmen. Dann lautet unsere Verarbeitungsmethode: Sie haben eine Ausnahme in Ihrem Programm, wenn Sie diese Ausnahme erhalten , was soll man tun? Wolltuch? Es hängt nur davon ab, ob die obere Ebene des Programms die Ausnahme behandeln soll. Wenn Sie nicht möchten, dass das Programm der oberen Ebene damit umgeht, wandeln Sie es in eine Laufzeitausnahme um und lösen Sie es aus, um dem Programm der oberen Ebene keine Probleme zu bereiten. Wenn Sie möchten, dass das Programm der oberen Ebene damit umgeht, Verwandeln Sie es in eine Ausnahme zur Kompilierungszeit und lösen Sie es direkt aus. Gehen Sie zu .
In der tatsächlichen Entwicklung ist es am besten, für jede Ebene eine benutzerdefinierte Ausnahme zu schreiben, z. B. das Anpassen einer Ausnahmeklasse – DaoException – in der Dao-Ebene (Datenzugriffsebene).
Erstellen Sie die Ausnahmeklasse DaoException im cn.itcast.Exception-Paket wie folgt:
Der DaoException-Klassencode lautet wie folgt:
public class DaoException extends RuntimeException { public DaoException() { // TODO Auto-generated constructor stub } public DaoException(String message) { super(message); // TODO Auto-generated constructor stub } public DaoException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } }123456789101112131415161718192021222324252627
Wir möchten anpassen ein Dao-Ausnahmewurf Go, warum? Der größte Vorteil beim Anpassen einer auszulösenden Dao-Ausnahme besteht darin, dass ich diese Ausnahme auslöse. Sobald jemand den Klassennamen der Ausnahme sieht, kann er erkennen, auf welcher Ebene das Problem liegt, und dies schnell lokalisieren Ebene, um das Problem zu finden. Am besten ist es, für jede Ebene eine benutzerdefinierte Ausnahme zu haben.
Ändern Sie dann die Methode public User find(String username, String password)
in der Klasse UserDaoJdbcImpl in:
@Override public User find(String username, String password) { Connection conn = null; Statement st = null; // PreparedStatement预防SQL注入 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) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 /* * 自定义一个Dao异常抛出去,为什么呢? * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名, * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。 */ throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } }1234567891011121314151617181920212223242526272829303132333435
我们以前在开发service层(service层对web层提供所有的业务服务)时,编写UserService接口的具体实现类——UserServiceImpl时,业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦(希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式),要解耦,有两种方法:
工厂模式
Spring
现在我们重点关注工厂模式。这时我们要定义一个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; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); ...... } }123456789101112131415161718192021
以上的代码好吗?显然这样做并不好,每次别人调用createDao方法,都会去读一下配置文件dao.properties,但此配置文件在整个系统里面只要读取一次就好,没必要老去读,即以下这行代码只需运行一次。
DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");1
那么这行代码可以放到静态代码块里。
现在将以上代码修改为:
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; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); ...... } }12345678910111213141516171819202122232425
以上这样做并没有不好,但是对于现在而言还有一种做法,DaoFactory这个类被设计成单例的,所以其构造函数仅执行一次,所以可放到DaoFactory这个类的构造函数里面。所以最后完整的代码如下:
public class DaoFactory { /* static { DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); } */ 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; } // UserDao.class(接口类型) // DepartmentDao.class(接口类型) public <T> T createDao(Class<T> clazz) { // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties"); String className = daoConfig.getProperty(name); try { T dao = (T) Class.forName(className).newInstance(); // 创建实例对象 return dao; } catch (Exception e) { throw new RuntimeException(e); } } }123456789101112131415161718192021222324252627282930313233343536373839
然后在src目录下创建一个dao.properties文件,如下图所示:
在db.properties中内容如下所示:
UserDao=cn.itcast.dao.impl.UserDaoJdbcImpl1
实现service层和dao层的解耦,UserService接口的具体实现类——UserServiceImpl只须修改为:
// 对web层提供所有的业务服务public class BusinessServiceImpl { /* * 业务逻辑层和数据访问层要解耦——希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式 * 要解耦,有两张方法: * 1. 工厂模式 * 2. spring */ // private UserDao dao = new UserDaoJdbcImpl(); private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class); // 对web层提供注册服务 public void register(User user) throws UserExistException { // 先判断当前要注册的用户是否存在 boolean b = dao.find(user.getUsername()); if(b) { /* * service层是由web层来调用的, * 发现当前要注册的用户已存在,要提醒给web层,web层给用户一个友好提示 * 希望web层一定要处理,处理之后给用户一个友好提示,所以抛一个编译时异常, * 抛运行时异常是不行的,因为web层可处理可不处理 */ throw new UserExistException(); // 发现要注册的用户已存在,则给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示。 } else { user.setPassword(ServiceUtils.md5(user.getPassword())); dao.add(user); } } // 对web层提供登录服务 public User login(String username, String password) { // aaa 123 password = ServiceUtils.md5(password); // 要把密码md5一把再找 return dao.find(username, password); } }123456789101112131415161718192021222324252627282930313233343536
SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
例如,statement存在sql注入攻击问题,假如登录用户名采用' or 1=1 or name='
。
对于防范SQL注入,可以采用PreparedStatement取代Statement。
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; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from users where username=? and password=?"; st = conn.prepareStatement(sql); // 预编译这条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); } }12345678910111213141516171819202122232425262728293031
到此为止,我们就可以完整地写出UserDaoJdbcImpl类了,其完整代码如下:
public class UserDaoJdbcImpl implements UserDao { @Override public void add(User user) { Connection conn = null; // Statement st = 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()); // st = conn.createStatement(); // String sql = "insert into users(id,username,password,email,birthday,nickname) values('"+user.getId()+"','"+user.getUsername()+"','"+user.getPassword()+"','"+user.getEmail()+"','"+user.getBirthday().toLocaleString()+"','"+user.getNickname()+"')"; // int num = st.executeUpdate(sql); int num = st.executeUpdate(); if(num < 1) { throw new RuntimeException("注册用户失败!!!"); } } catch (Exception e) { // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛 /* * 自定义一个Dao异常抛出去,为什么呢? * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名, * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。 */ /* * gosling Thinking In Java Spring * Spring作者——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理? * 如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。 * * 我们的处理方法——你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理? * 如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。 */ // throw new RuntimeException(e); throw new DaoException(e); } finally { JdbcUtils.release(conn, st, rs); } } // username='or 1=1 or username=' password="" @Override public User find(String username, String password) { Connection conn = null; PreparedStatement st = null; // PreparedStatement预防SQL注入 ResultSet rs = null; try { conn = JdbcUtils.getConnection(); String sql = "select * from users where username=? and password=?"; st = conn.prepareStatement(sql); // 预编译这条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); } } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
面试题: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"); // 在断点模式Watch System.out.println(user); } @Test public void testFindByUsername() { UserDao dao = new UserDaoJdbcImpl(); System.out.println(dao.find("bbbb")); } }12345678910111213141516171819202122232425262728
经测试,没发现任何错误。同样也要对业务逻辑层已编写好的部分代码进行测试,在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); } }12345678910111213141516171819202122232425262728
经测试,没发现任何错误。
到此,对基于Servlet+JSP+JavaBean开发模式的用户登录注册的升级改造圆满完成。
Das obige ist der detaillierte Inhalt vonAusführliche Erläuterung des Upgrade-Tutorials für die Benutzeranmeldungsregistrierung basierend auf dem Servlet+JSP+JavaBean-Entwicklungsmodell. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!