SSH旅程(六)Spring和struts结合(方案一)
最后更新于:2022-04-01 14:50:18
Spring和struts结合.
上篇中把spring应用到hibernate中,本篇中把spring应用到struts中,首先复习一下struts基础知识。
[Struts旅程(一)Struts简介和原理](http://blog.csdn.net/lovesummerforever/article/details/18942381)
[struts旅程(二)Struts登录示例](http://blog.csdn.net/lovesummerforever/article/details/17348871)
[Struts旅程(三)Struts表单处理器ActionForm(静态动态)](http://blog.csdn.net/lovesummerforever/article/details/18951649)
[Struts旅程(四)MVC向strutsMVC框架演变过程](http://blog.csdn.net/lovesummerforever/article/details/18963959)
[Struts旅程(五)struts控制器DispatchAction](http://blog.csdn.net/lovesummerforever/article/details/18967831)
[Struts旅程(六)Struts页面转发控制ActionForward和ActionMapping](http://blog.csdn.net/lovesummerforever/article/details/19125933)
Struts调用流程如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fbb61a4.jpg)
看到这幅图一下子就能了解了struts的原理。Spring的核心就是IOC容器和AOP,所以我们用spring主要是管理业务对象和事务的管理,所以主要是Model层来让spring管理,这是我们的一种方案。
**第一种集成方案在Action中取得beanFactory**
还记的在上篇文章中,测试的时候是在单元测试中拿到的BeanFactory,与struts结合就是在Action中取得beanFactory。步骤如下。
1、建立一个web项目。
2、建立相关页面,代码如下所示。
Login.jsp代码入下所示。
~~~
<%@ pagelanguage="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type" content="text/html;charset=GB18030">
<title>Insert titlehere</title>
</head>
<body>
<formaction="login.do" method="post">
用户:<input type="text"name="username"><br>
密码:<input type="password"name="password"><br>
<inputtype="submit" value="登录">
</form>
</body>
</html>
Login_success.jsp
<%@ pagelanguage="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<metahttp-equiv="Content-Type" content="text/html;charset=GB18030">
<title>Insert titlehere</title>
</head>
<body>
xx,用户登录成功!
</body>
</html>
~~~
3、配置struts环境,关于struts的配置,拷贝struts和jstl的依赖包;在web.xml中配置ActionServlet,提供struts-config.xml文件。前篇文中有说明,在此就不赘述了。
struts-config.xml代码如下所示。
~~~
<struts-config>
<form-beans>
<form-beanname="loginForm"type="com.bjpowernode.usermgr.web.forms.LoginActionForm"></form-bean>
</form-beans>
<action-mappings>
<actionpath="/login"
type="com.bjpowernode.usermgr.web.actions.LoginAction"
name="loginForm"
scope="request"
>
<forwardname="success" path="/login_success.jsp"/>
</action>
</action-mappings>
<message-resourcesparameter="resources.MessageResources" />
</struts-config>
~~~
4、配置spring环境,拷贝spring相关jar包,建立spring配置文件applicationContext-beans.xml。
applicationContext-beans.xml代码如下所示。
~~~
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<beanid="userManager"class="com.bjpowernode.usermgr.manager.UserManagerImpl"/>
</beans>
~~~
5、建立相关的Action和ActionForm。代码如下所示。
LoginAction.java代码如下所示。
~~~
public class LoginAction extendsAction {
@Override
publicActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
LoginActionFormlaf = (LoginActionForm)form;
Stringusername = laf.getUsername();
Stringpassword = laf.getPassword();
//但是我们每次都要去调用,去创建太麻烦了.
//我们在这里只需要去配置Listener就可以了,spring给实现好了.
BeanFactoryfactory = newClassPathXmlApplicationContext("applicationContext.xml");
UserManageruserManager = (UserManager)factory.getBean("userManager");
userManager.login(username,password);
}
}
LoginActionForm.java代码如下所示。
public class LoginActionFormextends ActionForm {
//表单上有什么提供什么属性.
//名字一定要与表单中的一样.
privateString username;
publicString getUsername() {
returnusername;
}
publicvoid setUsername(String username) {
this.username= username;
}
privateString password;
publicString getPassword() {
returnpassword;
}
publicvoid setPassword(String password) {
this.password= password;
}
}
~~~
6、建立业务逻辑层,代码如下所示。
UserManager代码如下所示。
~~~
public interface UserManager {
publicvoid login(String username, String password);
}
UserManagerImpl.java代码如下所示。
public class UserManagerImplimplements UserManager {
publicvoid login(String username, String password) {
System.out.println("UserManagerImpl"+"username="+ username);
}
}
~~~
7、 web.xml配置文件代码如下所示。
~~~
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
~~~
就这样我们在LoginAction中,使用beanFactory读取spring配置文件,找到UserManagerImpl实例。如果每次在Action中读取application-beans.xml文件,我们是否可以在服务器启动的时候就就创建BeanFactory呢?在这里我们可以使用spirng的工具WebApplicationContextUtils.getRequiredWebApplicationContext()从 ServletContext中 取得BeanFactory,然后再web.xml中配置Spring的Listener。
修改后,LoginAction代码如下所示。
~~~
public class LoginAction extendsAction {
@Override
publicActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
LoginActionFormlaf = (LoginActionForm)form;
Stringusername = laf.getUsername();
Stringpassword = laf.getPassword();
//用工具包直接拿出来就可以了。
BeanFactoryfactory =WebApplicationContextUtils.getRequiredWebApplicationContext(request.getSession().getServletContext());
UserManageruserManager = (UserManager)factory.getBean("userManager");
userManager.login(username,password);
returnmapping.findForward("success");
}
}
~~~
加入相关配置,web.xml代码如下所示。
~~~
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
~~~
第一种方案缺点:
我们在在Action中仍然看到Spring相关东西,看到Spring相关类,要是程序只看到的是接口,那要怎么做呢?
第二种方案,将struts的Aciton交给Spring来创建,让代理Action负责拿到beanFactory,根据Path名称到IOC中把对应的Action取出来。
SSH旅程(五)Spring运用到Hibernate中
最后更新于:2022-04-01 14:50:15
Spring和hibernate结合.
### 单纯Hibernate程序
1、首先是导入hibernate的jar包,步骤见[http://blog.csdn.net/lovesummerforever/article/details/19170795](http://blog.csdn.net/lovesummerforever/article/details/19170795),导入hibernate相关jar包。
2、建立用户和用户操作记录实体,Log.java和User.java。代码如下所示。
Log.java
~~~
import java.util.Date;
public class Log {
private int id;
//日志的类别.日志一般起到一个不可否认性.
//操作日志 安全日志 事件日志.
private String type;
private String detail;
private Date time;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
}
~~~
User.java
~~~
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
~~~
3、并建立与之对应的实体配置文件,Log.hbm.xml和Use.hbm.xml。代码如下所示。
~~~
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.bjpowernode.usermgr.domain.User" table="t_user">
<id name="id" >
<generator class="native"/>
</id>
<property name="name" />
</class>
</hibernate-mapping>
~~~
Log.hbm.xml
~~~
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.bjpowernode.usermgr.domain.Log" table="t_log">
<id name="id" >
<generator class="native"/>
</id>
<property name="type" />
<property name="detail" />
<property name="time" />
</class>
</hibernate-mapping>
~~~
4、Manager层代码如下所示。
LogManager.java接口
~~~
public interface LogManager {
//添加日志.方法
public void addLog(Log log);
}
~~~
LogManagerImpl实现
~~~
public class LogManagerImpl implements LogManager {
@Override
public void addLog(Log log) {
HibernateUtils.getSessionFactory().getCurrentSession().save(log);
}
}
~~~
UserManager接口
~~~
public interface UserManager {
public void addUser(User user);
}
~~~
UserManagerImpl.java实现
~~~
public class UserManagerImpl implements UserManager {
@Override
public void addUser(User user) {
Session session = null;
try {
//这个session中是放到threadlocal.
session = HibernateUtils.getSessionFactory().getCurrentSession();
session.beginTransaction();
// 网用户表中添加一条同时网日志中添加一条.
session.save(user);
Log log = new Log();
log.setType("操作日志");
log.setTime(new Date());
log.setDetail("xxx");
LogManager logManager = new LogManagerImpl();
//添加日志.
logManager.addLog(log);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
}
~~~
5、是通过sessionFactory来创建session,通过session来开启提交和关闭回滚事务,我们把session的开启关闭封装到一个工具类中。HibernateUtils.java代码如下所示。
~~~
public class HibernateUtils {
private static SessionFactory factory;
static {
try {
//读取hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure();
//建立SessionFactory
factory = cfg.buildSessionFactory();
}catch(Exception e) {
e.printStackTrace();
}
}
public static Session getSession() {
return factory.openSession();
}
public static void closeSession(Session session) {
if (session != null) {
if (session.isOpen()) {
session.close();
}
}
}
public static SessionFactory getSessionFactory() {
return factory;
}
}
~~~
6、配置hibernate.cfg.xml文件,包括数据库名称,数据库关联的表,以及用户名密码等。代码如下所示。
~~~
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/spring_hibernate_1</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.current_session_context_class">thread</property>
<!--
<property name="hibernate.current_session_context_class">jta</property>
-->
<mapping resource="com/bjpowernode/usermgr/domain/User.hbm.xml"/>
<mapping resource="com/bjpowernode/usermgr/domain/Log.hbm.xml"/>
</session-factory>
</hibernate-configuration>
~~~
7、使用junit进行单元测试,代码如下所示。
~~~
import junit.framework.TestCase;
public class UserManagerImplTest extends TestCase {
public void testAddUser() {
UserManager userManager = new UserManagerImpl();
User user = new User();
user.setName("张三");
userManager.addUser(user);
}
}
~~~
在上述操作用,对事物的控制边界在业务逻辑层,因为在UserManagerImpl中我们调用addUser()这一方法的同时要把这一操作写入到日志中,也就是调用了addLog()方法,而对于类的方法,一方法一session,一session一事务,那么如果控制事务的呢?我们要执行开启addUser()事务同时再开启addLog()事务吗?在这里我们没有再用以前的openSession方法,选择用的HibernateUtils.getSessionFactory().getCurrentSession();同时在hibernate.cfg.xml中对getCurrentSession()进行配置如下, <propertyname="hibernate.current_session_context_class">thread</property>表示在当前线程中,与当前线程绑定这样执行addUser()方法和addLog()方法使用的是同一个事务。
那OpenSession和getCurrentSession的区别呢?
1、openSession必须关闭,currentSession在事务结束后自动关闭。
2、openSession没有和当前线程绑定,currentSession和当前线程绑定。并且使用currentSession需要在我们的hibernate.cfg.xml文件中进行事务的配置,是使用Jdbc事务还是JTA事务。
### hibernate和spring结合使用
我们从上述例子中发现,hibernate的事务是独立于hibernate对数据库的增删改查的,并且事务控制在我们的业务逻辑层,对于独立的东西,像是横切性问题,自然想到了AOP,实际上SpringAOP封装了对事务的管理,使用SpringAOP我们不再负责去开启和关闭事务。
下面用SpringAOP来和hibernate结合。
当然也要导入Spring相关jar,[前篇](http://blog.csdn.net/lovesummerforever/article/details/22646793)文章已经叙述了。
对于事务管理就是一个横切性问题,把事务管理模块化就是我们的aspect,然后再配置文件中进行配置,我们可以把事务单独放到一个配置文件中。
### 1、代码如下,applicationContext-common.xml文件。
~~~
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- 配置SessionFactoyr -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory"/>
</property>
</bean>
<!-- 哪些类哪些方法使用事务. -->
<aop:config>
<aop:pointcut id="allManagerMethod" expression="execution(* com.bjpowernode.usermgr.manager.*.*(..))"/>
<aop:advisor pointcut-ref="allManagerMethod" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
</beans>
~~~
首先是配置的是sessionFactory,让spring拿到hibernate的sessionFactory,以便对hibernate事务控制,通过sessionFactory产生session再访问。这样就把把hibernate的sessionFactory注入到Spring中了。通过<property>标签,告诉SpringHibernate的配置文件在哪里,以便spring可以读取hibernate的配置文件。
其次是配置事务管理器,把我们的sessionFactory注入给事务管理器,让事务管理器管理事务。
其次,到底是哪些类哪些方法,开始执行的时候执行事务,<aop:pointcutid="allManagerMethod"
其次,配置AOP,expression="execution(*com.bjpowernode.usermgr.manager.*.*(..))",到底是哪些类交给spring完成事务管理?我们应用在所有的manager包中的所有方法上,manager所有类所有方法全部参与事务的运行。那在什么地方触发开启事务?
再次,定义一个Advice,配置事务的传播特性,例如addUser()中调用addLog()方法,是在同一个事务还是不在同一个事务。以add开头,del开头,modify开头以及其他,我们配置的事务传播特性为propagation="REQUIRED",这样在一个方法中调用另一个方法他们公共一个线程。
### 2、让UserManagerImpl继承spring提供的对hibernateDao支持类。
HibernateDaoSupport,这样继承之后我们就能拿到session,其实也就是hibernate中的session,只不过spring为我们封装了。我们可以这样拿到sesion:This.getSession().save(user);或者使用spring封装好的对象:This.getHibernateTemplate().save(user);这样都封装到里面了,我们不管理事务的开启和关闭。
之前在我们的UserManagerImpl中使用了LogManagerImpl实例,这次我们可以使用Spring的IOC容器,把他们之间的依赖关系注入到Spring中,这样就看不到实例,面对接口编程,进行了解耦。
接口不变,UserManagerImpl.java代码如下所示。
~~~
public class UserManagerImpl extends HibernateDaoSupport implements UserManager {
private LogManager logManager;
public void setLogManager(LogManager logManager)
{
this.logManager = logManager;
}
@Override
public void addUser(User user)throws Exception {
//this.getSession().save(user);
//或者用.
this.getHibernateTemplate().save(user);
Log log = new Log();
log.setType("操作日志");
log.setTime(new Date());
log.setDetail("xxx");
//LogManager logManager = new LogManagerImpl();
//添加日志.
logManager.addLog(log);
//运行期的异常,会回滚. 并且是他的子类也会回滚.
//throw new RuntimeException();
//throw new Exception();
}
}
~~~
LogManagerImpl.java 代码如下所示。
~~~
public class LogManagerImpl extends HibernateDaoSupport implements LogManager {
@Override
public void addLog(Log log) {
//getSession().save(log);
this.getHibernateTemplate().save(log);
}
}
~~~
删除我们自己建立的HibernateUtils.java类,删除hibernate.cfg.xml文件中对getCurrentSession()的事务配置。
### 3、在配置文件中配置依赖关系。
applicationContext-beans.xml代码如下所示。
~~~
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.bjpowernode.usermgr.manager.UserManagerImpl">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="logManager" ref="logManager"/>
</bean>
<bean id="logManager" class="com.bjpowernode.usermgr.manager.LogManagerImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
~~~
在Junit中测试程序代码如下所示。
~~~
public class UserManagerImplTest extends TestCase {
public void testAddUser() {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext-*.xml");
UserManager userManager = (UserManager) factory.getBean("userManager");
User user = new User();
user.setName("张三");
try {
userManager.addUser(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
~~~
显示结果如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fb9726f.jpg)
这样就完成了spring和hibernate的结合,主要是利用SpringAOP对hibernate的事务进行控制和在Manager层之间的调用用Spring IOC进行控制。
下一篇Spring和Struts结合。
Spring旅程(四) AOP–Spring AOP实例
最后更新于:2022-04-01 14:50:13
上篇讲述了[Spring的AOP](http://blog.csdn.net/lovesummerforever/article/details/22664647)[原理](http://blog.csdn.net/lovesummerforever/article/details/22664647),本篇将上篇中使用的动态代理解决独立服务的问题用SpirngAOP来实现。
### 采用配置文件的方式。
1、导入相应的Spring jar包。
2、[在SpringIOC中的步骤123中已经给出](http://blog.csdn.net/lovesummerforever/article/details/22646793)。
3、将横切性关注的问题模块化,建立安全处理类。在SecurityHandler类中写我们的独立方法,也就是定义Advice(具体实现),代码如下。
~~~
public class SecurityHandler {
private void checkSecurity() {
System.out.println("-------checkSecurity-------");
}
}
~~~
4、在配置文件中进行相关配置。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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userManager" class="com.bjpowernode.spring.UserManagerImpl"/>
<bean id="securityHandler" class="com.bjpowernode.spring.SecurityHandler"/>
<aop:config>
<aop:aspect id="secutityAspect" ref="securityHandler">
<!-- 以add开头的方法 <aop:pointcut id="addAddMethod" expression="execution(* add*(..))"/> -->
<!-- com.bjpowernode.spring.*包下的所有方法.
<aop:pointcut id="addAddMethod" expression="execution(* com.bjpowernode.spring.*.*(..))"/>
-->
<aop:pointcut id="addAddMethod" expression="execution(* com.bjpowernode.spring.*.add*(..)) || execution(* com.bjpowernode.spring.*.del*(..))"/>
<aop:before method="checkSecurity" pointcut-ref="addAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
~~~
需要说明几点
指定SecutityHander为aspect: <aop:aspectid="secutityAspect" ref="securityHandler">
Pointcut(切入点)以及事务范围,在这里用于所有的add和del方法上:<aop:pointcutid="addAddMethod" expression="execution(*com.bjpowernode.spring.*.add*(..)) || execution(*com.bjpowernode.spring.*.del*(..))"/>
设置通知类型并指向哪个切入点:
<aop:beforemethod="checkSecurity" pointcut-ref="addAddMethod"/>
将目标类UsemrManagerImpl和Aspect类SecurityHandler配置到Spring的IOC中:<beanid="userManager"class="com.bjpowernode.spring.UserManagerImpl"/>
<bean id="securityHandler"class="com.bjpowernode.spring.SecurityHandler"/>
5、客户端调用代码如下所示。
~~~
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.delUser(1);
}
}
~~~
上述基本上完成了一个SpringAOP的实例。
### 如果我们在advice中,也就是在我们的安全性方法中想要获取客户端的数据该如何获取呢?
我们可以在这个方法中添加参数,也就是我们JoinPoint,通过传递这个对象,我们可以去得到参数值,SecurityHandler类的代码如下所示。
~~~
public class SecurityHandler {
private void checkSecurity(JoinPoint joinPoint) {
//取得参数.
for(int i=0;i<joinPoint.getArgs().length;i++)
{
System.out.println(joinPoint.getArgs()[i]);
}
System.out.println(joinPoint.getSignature().getName());
System.out.println("-------checkSecurity-------");
}
}
~~~
### 当然我们也可以采用注解的方式来实现。
1、采用注解的方式首先要加入一些jar包,*Spring_home/spring-framework-2.0\lib\aspectj\aspectjrt.jar aspectjweaver.jar
2、SecurityHandler类的代码如下所示。和上面的通过配置文件方式进行对比,很快就会懂得不同的标签不同的含义。
~~~
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SecurityHandler {
/**
* 定义Pointcut,Pointcut的名称为addAddMethod(),此方法没有返回值和参数
* 该方法就是一个标识,不进行调用
*/
@Pointcut("execution(* add*(..))")
private void addAddMethod(){};
/**
* 定义Advice,表示我们的Advice应用到哪些Pointcut订阅的Joinpoint上
*/
@Before("addAddMethod()")
//@After("addAddMethod()")
private void checkSecurity() {
System.out.println("-------checkSecurity-------");
}
}
~~~
3、applicationContext.xml文件中配置aspect类和目标类UserManagerImpl,同时配置实用Annotation方式,代码如下所示。
~~~
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<!-- 启用AspectJ对Annotation的支持 -->
<aop:aspectj-autoproxy/>
<bean id="userManager" class="com.bjpowernode.spring.UserManagerImpl"/>
<bean id="securityHandler" class="com.bjpowernode.spring.SecurityHandler"/>
</beans>
~~~
4、客户端调用和配置XML方式一样。
是不是关于SpirngAOP,渐渐的懂些了。
Spring旅程(三) AOP–Spring AOP容器基础
最后更新于:2022-04-01 14:50:11
上篇讲述了[Spring的IOC](http://blog.csdn.net/lovesummerforever/article/details/22500339)[原理](http://blog.csdn.net/lovesummerforever/article/details/22500339)和[使用](http://blog.csdn.net/lovesummerforever/article/details/22646793),本篇讲述Spring对AOP的支持。首先回顾一下Spring IOC容器,用一种通俗的方式理解Spring的IOC,也就是家里要安装灯泡,去网上买,我们只需要去下订单就(ApplicationContext.xml)可以了,无需关心工厂是如何加工的,你想要灯泡发红的光就直接在选择的时候选择红光,如果想要发黄色光的就直接选择发黄色光的灯牌,之后生成订单后会有派件人员直接派送到你的家门口,不需要你自己创建灯泡工厂去生产(new)灯泡。
### 那什么是Spring的AOP呢?
我们可以理解为你想要给灯安装一个灯罩,可以直接把灯罩起来,而这个灯罩相对于灯本身来说没有任何的关系,是独立存在的,你只要加上去就可以。对于这个灯罩来说,就是从AOP的角度去分析了。
### 那究竟什么是Spring的IOC容器呢?
在软件业,AOP是AspectOriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。也可以意为面向行为编程,是函数式编程的一种衍生范型。
### 我们以一个例子来说明什么是AOP。
例如我们在用户增删改查上添加一个安全性检查,这样无论是任何操作之前都要进行安全性检查。
UserManager.java接口代码如下所示。
~~~
public interface UserManager {
public void addUser(String name,String password);
public void delUser(int userId);
public String findUserById(int userId);
public void modifyUser(int userId,String username,String password);
}
~~~
UserManagerImpl.java实现接口代码如下所示。
~~~
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String name, String password) {
checkSecurity();
System.out.println("------------UserManagerImpl.add()----");
}
@Override
public void delUser(int userId) {
checkSecurity();
System.out.println("------------UserManagerImpl.del()----");
}
@Override
public String findUserById(int userId) {
checkSecurity();
System.out.println("------------UserManagerImpl.findUserById()----");
return "张三";
}
@Override
public void modifyUser(int userId, String username, String password) {
checkSecurity();
System.out.println("------------UserManagerImpl.ModifyUser()----");
}
//检查安全性.
//当安全性不需要时就要成百上千给去改动,
//我们就可以用代理模式,让代理去做,让代理去控制目标,代理做某件事情.
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
这样的话如果我们想要更改的话就要在这个类上对安全性检查进行更改,添加的话也需要修改这个类的代码进行添加,这不符合我们设计原则,开闭原则,对扩展开放,对修改关闭。那怎么办呢?我们从中可以发现,检查安全性来说对增删改查的操作没有影响,是一项独立存在的方法或独立存在的服务,这样我们可以把检查安全性这以服务抽取出来放到UserManagerImpl类的代理类中,这样去分开这个独立的服务。让代理去实现相同的接口,调用UserManagerImpl中的方法,把服务单独的放到代理类中。这样单独的管理这项服务。
### 使用静态代理解决问题
代理类代码如下所示。UserManagerImplProxy.java,加上代理类之后我们就可以去掉UsermanagerImpl.java中的安全性检查方法。
~~~
/**
* 代理类和目标做的事情是一样的,所以实现相同的接口.
* @author Summer
*
*/
public class UserManagerImplProxy implements UserManager {
//目标引用.
private UserManager userManager;
//通过构造方法传递过来.
public UserManagerImplProxy(UserManager userManager)
{
this.userManager = userManager;
}
@Override
public void addUser(String username, String password) {
checkSecurity();
userManager.addUser(username, password);
}
@Override
public void delUser(int userId) {
// TODO Auto-generated method stub
checkSecurity();
userManager.delUser(userId);
}
@Override
public String findUserById(int userId) {
checkSecurity();
return userManager.findUserById(userId);
}
@Override
public void modifyUser(int userId, String username, String password) {
checkSecurity();
userManager.modifyUser(userId, username, password);
}
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
但问题是要去修改的这个安全性检查的方法的话,就要去修改代理类了,如果很多方法,就要修改很多次,会引来同样的开闭问题。
我们应该剥离出来,让这个方法在运行的时候动态的加入进去,方法只写一次,从AOP的角度来说,这种遍布在系统中的独立的服务,称为横切性的关注点。纵向的是方法一调用方法二,方法二调用方法三,而这个服务是切入到各个方法上的,所以认为是横向的。我们可以使用动态代理来解决这个问题。
### 使用动态代理解决问题
动态代理,我们要实现一个系统的处理器InvocationHandler,它可以复用,不需要每个方法一个代理,并且是在运行时声明出来的,可以为各个接口服务。把横切性的问题拿出来放到一个单独的类中,上述例子中,我们删除我们自己手动写的代理类,新建SecurityHandler类,实现InvocationHandler这个接口。
代码如下所示。
~~~
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class SecurityHandler implements InvocationHandler {
private Object targetObject;
public Object createProxyInstance(Object targetobject)
{
this.targetObject = targetobject;
//根据目标生成代理.
//代理是对接口来说的,是拿到目标的接口.拿到这个接口,实现这个接口.
//targetobject这个目标类实现了哪个接口.
//返回代理.
return Proxy.newProxyInstance(targetobject.getClass().getClassLoader(),
targetobject.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//检查安全性.
checkSecurity();
//当前方法,调用目标对象的方法.
//调用目标方法.
Object ret = method.invoke(targetObject, args);
return ret;
}
//检查安全性.
private void checkSecurity()
{
System.out.println("------------UserManagerImpl.checkSecurity()----");
}
}
~~~
这个代理类是运行时创建的,是动态的,而编译时创建的是静态的。TargetObject目标,建立目标实例createProxyInstance(),根据目标生成代理Proxy.newProxyInstance(),在invoke()方法中设置允不允许调用真正的对象,在invoke()中进行检查安全性控制,在invoke()中统一做控制,就像Filter中的doFilter方法。
客户端调用,代码如下所示。
~~~
<pre name="code" class="java">public class Client {
/**
* @param args
*/
public static void main(String[] args) {
InvocationHandler hander = new SecurityHandler();
//返回的是一个代理类,不是真正的UserManager.而是指向代理类.在内存中.
UserManager userManager = (UserManager)hander.createProxyInstance(new UserManagerImpl());
//使用的是代理.
//共同的接口,不同的实现.
userManager.addUser("张三", "123");
}
}
~~~
~~~
//返回的是一个代理类,不是真正的UserManager.而是指向代理类.在内存中.UserManager userManager = (UserManager)hander.createProxyInstance(new UserManagerImpl());//使用的是代理.//共同的接口,不同的实现.userManager.addUser("张三", "123");}}
~~~
接下来让我们走入Spring的AOP,他是如何实现的呢?如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fb4b635.jpg)
### AOP中的基本概念和术语。
### 1、切面(Aspect)
切面就是一个抽象出来的功能模块的实现,例如上述安全性检查,可以把这个功能从系统中抽象出来,用一个独立的模块描述,这个独立的模块就是切面,我们的SecutiryHinder类。
### 2、连接点(JoinPoint)
连接点即程序运行过程中的某个阶段点,可以是方法调用、异常抛出等,在这个阶段点可以引入切面,从而给系统增加新的服务功能。
### 3、通知(Advice)
通知即在某个连接点所采用的处理逻辑,即切面功能的实际实现。通知的类型包括Before、After、Around以及Throw四种,下篇文章将会展示通知的使用方法。
### 4、切入点(PointCut)
接入点即一系列连接点的集合,它指明通知将在何时被触发。可以指定能类名,也可以是匹配类名和方法名的正则表达式。当匹配的类或者方法被调用的时候,就会在这个匹配的地方应用通知。
### 5、目标对象(Target)
目标对象就是被通知的对象,在AOP中,目标对象可以专心实现自身的业务逻辑,通知功能可以在程序运行期间自动引入。
### 6、代理(Proxy)
代理是目标对象中使用通知以后创建的对象,这个对象既拥有目标对象的全部功能,而且拥有通知提供的附加功能。
理解上述术语还是先用比较通俗的方式来解释,对于切面,我们在中学中学习过,切线,切面,切点,对于数学中学习的我们一定都不陌生。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fb77180.jpg)
如果仍然还是抽象的话,我们知道嫦娥一号与月球对接,嫦娥一号所要完成的任务或功能就是我们的切面(Aspect),带探究月球生命的使命;连接点(JoinPoint)就是总台发出命令,来触发嫦娥一号升空,这个发出的命令触发点就是JoinPoint;通知(Advice)就是具体的去执行总台的命令,包括集中类型是在总台发出命令之前(Before)、之后(After)、Around(大约多长时间)、或系统出现错误(Throw),去执行具体的操作。切入点(PointCut)就是总台发出取出月球特殊植物,嫦娥一号进行匹配特殊的植物,并通知是否找到成功或失败。那嫦娥一号这个对象本身就是我们的代理(Proxy),而真正的目标对象是总台(大概理解一下,不到之处,望原谅)。
我们这里的横切性关注点(Coreconcerns)是指安全性服务,对于安全性的检查可能分不到各个模块中。而切面(Aspect)是一个关注点的模块化,从软件角度来说是指应用程序在不同模块的某一个领域或方面。连接点(Joinpoint)程序执行过程中某个特殊的点,比如方法调用或处理异常的时候,上述我们再调用增删改查方法的时候。切入点(Pointcuts)是连接点的集合。通知(Advice)是实际的逻辑实现,即实际实现安全服务的那个方法。
通知有四种类型,前置通知(Beforeadvice):在切入点匹配方法之前使用。返回后通知(Afterreturning advice):在切入点匹配方法返回的时候执行。抛出后通知(Afterthrowing advice):在切入点匹配的方法执行时抛出异常的时候运行。后通知(After(finally)advice):无论切入点匹配方法是否正常结束,还是抛出异常结束的,在它结束(finally)后通知(afteradvice)。环绕通知(AroundAdvice):环绕通知既在切入点匹配的方法执行之前又再执行之后运行。并且可以决定这个方法在什么时候执行,如何执行,设置是是否执行。
晦涩难懂的东西还是用实例来说明,见下一篇SpringAOP实例。
Spring旅程(二)非Spring向Spring过渡– Spring IOC容器的使用
最后更新于:2022-04-01 14:50:08
### 上篇代码的缺点
[上一篇我们以一个简单的例子讲述了Spring](http://blog.csdn.net/lovesummerforever/article/details/22500339)[的由来](http://blog.csdn.net/lovesummerforever/article/details/22500339),上篇中UserManager调用Dao的时候需要自己去实例化具体的Dao,或者通过工厂创建相应的Dao,最终还是客户端自己去实例化具体的UserManager,UserManager自己去拿到工厂,工厂自己去创建相应的Dao。而Spring框架封装了这创建过程,不再是我们自己手动去new,而是交给Spring的IOC容器去做这件事情,他自己(SpringIOC容器)会找,并且根据配置文件找到后new好直接传递给我们,不需要我们自己拿工厂去找去实例化。
### 那什么是Spring的IOC容器呢?
IOC(Inversionof Control),这个名词就叫做控制反转,控制反转还有一个名字叫做依赖注入DI(Dependencyinjection)。上篇代码中我们在UserManagerImpl中这样调用Dao层,UserDaouserDao = new UserDaoOracleImpl();这样使用接口编程可以增加代码的稳定和健壮性等等,但是接口一定是需要实现的,迟早要执行这句代码,这样一来耦合关系就产生了。ManagerImpl类与UserDaoOracleImpl类就是依赖关系,当然我们可以使用工厂来创建Dao,如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fae7743.jpg)
图2.1未使用工厂创建Dao
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fb19a3c.jpg)
图2.2使用工厂来创建Dao
使用工厂表面上一定程度解决了以上的问题,但实质上代码耦合没有改变。而通过IOC容器可以彻底解决这种耦合,是把耦合的代码移出去,放到统一的XML文件中,通过一个容器在需要的时候把依赖关系形成,即把需要的接口注入到需要的类中,这就是我们说的“依赖注入”的来源了。IOC容器有很多种,PicoContainer、JBoss、HiveMind、spring,在这里我们主要说的是Spring的IOC容器。
### 下面是使用Spring IOC容器来控制,如下步骤所示。
1、导入Spring依赖包(因为在这里使用的是Spring的IOC,所以导入核心包就可以了)
Spring_home/dist/spring.jar.
另外引入相关日志记录包,两个,spring-framework-2.0\lib\log4j\log4j-1.2.14.jar spring-framework-2.0\lib\jakarta-commons\commons-logging.jar
直接在UserLibraries中建立userlibraries然后再导入JARs,之后引入到我们的项目中。
2、提供配置文件applicationContext.xml,放到我们项目的src目录下,我们可以拷贝Spring spring-framework-2.0\samples\jpetstore\war\WEB-INF
applicationContext.xml到我们的src目录下。
3、提供log4j.propertiers配置文件。
4、在UserManagerImpl中提供构造方法或setter方法,让Spring将UserDao与UserManagerImpl的关系注入(DI)进来。
5、让Spring管理对象的创建和依赖,必须将依赖关系配置到Spring的核心配置文件中。
[在上一篇代码中](http://blog.csdn.net/lovesummerforever/article/details/22500339),我们的Dao层代码不变,我们的UserManagerImpl代码如下所示。
### UserManagerImpl.java
~~~
public class UserManagerImpl implements UserManager {
//这样改看不到具体的实现了. 但是在client调用时看到组装的过程.
private UserDao userDao;
//构造方法进行赋值.
// public UserManagerImpl(UserDao userDao) {
//
// this.userDao = userDao;
// }
//
//通过setter方法来把依赖对象注入。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String username, String password) {
userDao.addUser(username, password);
}
}
~~~
Spring的依赖注入方法,我们可以通过赋值注入,也就是通过getter和setter方法来获得和设置Bean的属性值,我们一般使用这种方法。在Spring中每个对象在配置文件中,均以<bean>的形式出现,而子元素<property>则指明了使用这个JavaBean的setter方法来注入值,在<property>中,可以定义要配置的属性以及要注入的值,可以将属性注入任何类型的值,不仅是基本类型的java数据类型,也可以是Bean对象,在这就我们注入的就是Bean对象。
另一种是构造器的注入,不常使用,通过标签<constructor-arg>来实例化这个Bean的时候需要注入的参数。
### 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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
<bean id="userDao4MySql" class="com.bjpowernode.spring.dao.UserDao4MySqlImpl"/>
<bean id="userDao4Oracle" class="com.bjpowernode.spring.dao.UserDao4OracleImpl"/>
<!-- userManager依赖于mysql的实现 -->
<bean id="userManager" class="com.bjpowernode.spring.manager.UserManagerImpl">
<!-- <constructor-arg ref="userDao4MySql"/> -->
<!-- <constructor-arg ref="UserDao4Oracle"/>-->
<property name="userDao" ref="userDao4MySql"/>
</bean>
</beans>
~~~
上面这个XML文件就是Spring中用来配置JavaBean的。通过这样的一个方式可以把分散的JavaBean组装一个整体系统,再去实现具体的业务了逻辑。完成了Bean的开发和装配下一步就是要进行调用和测试了。
### 在客户端进行调用,代码如下所示。
~~~
public class client {
public static void main(String[] args) {
//spring中有BeanFactory,是一个接口.
//专门读取applicationContext.xml文件的实现.
//ClassPathXmlApplicationContext类就是对BeanFactory接口的实现.
//会把applicationContext.xml这个文件读进来,并且创建对象.
BeanFactory factory = new ClassPathXmlApplicationContext("applicationContext.xml");
//产品标识.
UserManager userManager = (UserManager)factory.getBean("userManager");
userManager.addUser("张三", "123");
}
}
~~~
这样就完成了用SpringIOC容器来解决依赖问题,不需要我们自己去建立工厂,我们只要声明一个接口,具体的实现在配置文件中告诉SpringIOC容器,他会自己找到并进行创建相应的实例,然后传递给我们,本来是由我们自己去控制对象之间的依赖关系,现在由IOC来控制了,这就是控制反转。这样我们很轻松就了解了哦。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fb357a7.jpg)
Spring coming.....
Spring旅程(一)为什么使用Spring
最后更新于:2022-04-01 14:50:06
### Spring框架
Spring有春天、弹簧、跳跃和泉眼的意思。Spring是一个开源的框架,是为了解决企业应用程序开发复杂性由RodJohnson创建的。虽然Spring是为企业级应用推出的,但是所有的java系统开发都可以使用Spring,包括桌面应用程序和企业级Web应用。Spring总不是凭空而出现的,任何框架也是,前面我们所说的struts和hibernate都有他出现的原因,以及是哪些问题促使了人们开发出新的框架。那为什么使用Spring框架呢?
### 从抽象工厂开始说起
曾近我们在Dao层为了解决更换数据库的难题,我们自己动手写工厂,让工厂去创建从而在客户端调用时隐藏了具体的创建细节,这样我们就可以更换数据库了。在Service层也同样是可以用到抽象工厂,为了适应用户需求变动,我们在sevice层应用抽象工厂,让web调用只调用接口,具体创建放到工厂里,这样我们就可以通过更换不同的.class文件来更换不同的业务实现。下面我们就简单的通过一个程序来说明。
Dao层
UserDao接口代码如下所示。
~~~
public interface UserDao {
public void addUser(String username,String password);
}
Dao层UserDao实现UserDaoMySqlImpl.java
public class UserDao4MySqlImpl implements UserDao {
@Override
public void addUser(String username, String password) {
System.out.println("UserDao4MySqlImpl.addUser()");
}
}
~~~
Dao层UserDao实现UserDaoOracleImpl.java
~~~
public class UserDao4OracleImpl implements UserDao {
@Override
public void addUser(String username, String password) {
System.out.println("UserDao4OracleImpl.addUser()");
}
}
~~~
这样我们创建了Dao层的接口,并且给出了对于不同数据库驱动的不同实现。
业务逻辑层manager(在这里先不引入工厂)
Manager层接口UserManager.java
~~~
public interface UserManager {
public void addUser(Stringusername,String password);
}
~~~
Manager层实现UserManagerImpl.java
~~~
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String username, String password) {
//服务(对象)定位,由我们的应用程序负责服务(对象)定位.//主动
//UserDao userDao = new UserDao4MySqlImpl();
//如果想要换实现,就要可以换了.
UserDao userDao = new UserDao4OracleImpl();
userDao.addUser(username,password);
}
}
~~~
在这里尚且没有使用抽象工厂+配置文件,来看我们的客户端调用。
~~~
public class client {
/**
* @param args
*/
public static void main(String[] args) {
//TODO Auto-generated method stub
//服务(对象)定位,由我们的应用程序负责服务(对象)定位.
//因为自己产生了主动的查找.所以必须依赖,并且写死.
UserManager userManager = new UserManagerImpl();
userManager.addUser("张三","123");
}
}
~~~
我们如果要更换数据库需要打开UserManagerImpl类去更改不同的实现,同样在client调用如果要更改不同的业务也需要不同的UserManager实现。即便我们加上抽象工厂+配置文件同样同样需要在界面调service层使用和在调用Dao层使用,让程序变的更加复杂,每个项目都要去写,多个项目也是如此,当业务逻辑复杂的时候,我们就需要些多个工厂去创建不同的产品,并且在使用工厂的时候还要注意每次创建保证一个实例,这所有的一切一切,即便是工厂都是我们程序员手动去写的,这样就比较重复了,是我们去管理这些类,为什么不让机器去管理呢?
所以Spring应运而生,Spring封装了更多的重复和复杂,不但封装了工厂,并且封装了创建时出现的线程安全问题,Spring可以管理Connection,在使用hibernate的时候我们需要管理session,一线程一session,session连接我们lazy的有效期,spring提供一个Filter,我们不用再管理session的创建和销毁,spring帮我们去管理了。
### 下面大概了解一下Spring中将会时常提起的一些名词。
简单的来说,Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。
**轻量级**:大小与开销方面而言Spring是轻量的。
**非侵入式**:Spring应用中的对象不依赖于Spring的特定类。
**控制反转**:Spring通过一种称为控制反转(IOC)的技术促进了松耦合。当应用了IOC一个对象依赖的其他对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
**切面编程**:Spring提供了切面编程的丰富支持,允许分离应用的业务逻辑与服务。
**容器**:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建—一个可配置的原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例,以及它们是如何关联的。
**框架**:Spring可以将简单的组件配置,组成为复杂的应用,在Spring中应用对象被声明式组合,典型地是在一个XML文件里,Spring提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了我们。
大概知道这些名词吧,后续的文章会应用实例详细的说明Spring的功能。
下篇我们将把上述的例子用Spring来实现,从而对比出Spring的IOC强大之处。
Hibernate旅程(九)Hibernate缓存机制–查询缓存
最后更新于:2022-04-01 14:50:04
Hibernate查询缓存
我们介绍了[Hibernate](http://blog.csdn.net/lovesummerforever/article/details/20997879)[一级缓存](http://blog.csdn.net/lovesummerforever/article/details/20997879),[二级缓存](http://blog.csdn.net/lovesummerforever/article/details/21158769)。而hibernate二级缓存时针对Id查询的缓存策略,对于条件查询则毫无作用。因此hibernate提供了针对条件查询的QueryCache(查询策略)。
下面来看session控制的查询缓存。
### 一、查询缓存配置
1、在hibernate.cfg.xml中加入查询缓存的策略,<propertyname="hibernate.cache.use_query_cache">true</property> 启用查询缓存的策略,默认是false。
### 二、关闭二级缓存,采用query.list()查询普通属性
代码如下所示。
~~~
public voidtestCache1() {
Session session = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Listnames = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0;i<names.size(); i++) {
Stringname = (String)names.get(i);
System.out.println(name);
}
System.out.println("-------------------------------------------------------");
//不会发出查询语句,因为启用查询缓存
names= session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0;i<names.size(); i++) {
Stringname = (String)names.get(i);
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
我们可以看到控制台输出语句,仅输出一次:Hibernate: select student0_.name as col_0_0_ fromt_student student0_
由此可知,我们开启了查询缓存,第一次进行查询的时候,已经把结果放到querycache中,当第二次再次做出相同的查询的时候,就不再向数据库发重复的sql语句了。
### 三、关闭二级缓存,启用查询缓存,采用query.list()查询普通属性
代码就如下所示。
~~~
public voidtestCache2() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Listnames = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0;i<names.size(); i++) {
Stringname = (String)names.get(i);
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------------------------");
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//不会发出查询语句,因为查询缓存和session的生命周期没有关系
Listnames = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0;i<names.size(); i++) {
Stringname = (String)names.get(i);
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
运行结果如下所示。
控制台打印结果:
select student0_.name as col_0_0_ fromt_student student0_
班级0的学生0
班级0的学生1
班级0的学生2
班级0的学生3
班级0的学生4
班级0的学生5…
我们可以看出,同样,只打印一次查询语句,如果没有开启查询缓存的话,并且关闭二级缓存的情况下,还会去数据库再查询一遍,而我们的程序中没有再去重复的去数据库中查询的原因是,当开启query缓存的时候,查询缓存的生命周期与session无关。
### 四、关闭二级缓存,开启查询,采用query.iterate()查询普通属性
代码如下所示。
~~~
public voidtestCache3() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Iteratoriter = session.createQuery("select s.name from Student s")
.setCacheable(true)
.iterate();
while(iter.hasNext()){
Stringname = (String)iter.next();
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------------------------");
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//会发出查询语句,query.iterate()查询普通属性它不会使用查询缓存
//查询缓存只对query.list()起作用
Iteratoriter = session.createQuery("select s.name from Student s")
.setCacheable(true)
.iterate();
while(iter.hasNext()){
Stringname = (String)iter.next();
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
显控制台显示结果打印了两次sql语句。
-------------------------------------------------------
Hibernate: select student0_.name as col_0_0_from t_student student0_
根据这样的结果我们发现,quer.iterate()查询普通属性它是不会使用查询缓存,查询缓存只对query.list()起作用。
### 五、关闭二级缓存,关闭查询缓存,采用query.list()查询实体
代码如下所示。
~~~
public voidtestCache4() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
List students =session.createQuery("select s from Student s")
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------------------------");
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//会发出查询语句,默认query.list()每次执行都会发出查询语句
List students =session.createQuery("select s from Student s")
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
显示结果如下所示。
控制台上打印两次sql语句。
Hibernate:select student0_.id as id0_, student0_.name as name0_, student0_.classesid asclassesid0_ from t_student student0_
班级0的学生0
班级0的学生1
班级0的学生2
班级0的学生3
班级0的学生4
由此可知,不开启查询缓存,默认query.list每次执行都会发出查询语句。
### 六、关闭二级缓存,开启查询缓存,采用query.list()查询实体
代码如下所示。
~~~
Session session = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Liststudents = session.createQuery("select s from Student s")
.setCacheable(true)
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------------------------");
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//会发出n条查询语句,因为开启了查询缓存,关闭了二级缓存,那么查询缓存就会缓存实体对象的id
//第二次执行query.list(),将查询缓存中的id依次取出,分别到一级缓存和二级缓存中查询相应的实体
//对象,如果存在就使用缓存中的实体对象,否则根据id发出查询学生的语句
Liststudents = session.createQuery("select s from Student s")
.setCacheable(true)
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
~~~
控制台打印sql如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fac33c8.jpg)
在第一次查询的时候,发出一条sql语句查询出结果,因为我们开启了查询缓存,会把第一次查询出的实体结果集的id放到查询缓存中,第二次再次执行query.list()的时候,会把id拿出来,到相应的缓存去找,因为是跨session,在二级缓存中找不到,所以每次都会发出查询语句,二级缓存中不存在,有多少个id就会发出查询语句多少次。
### 七、开启二级缓存,开启查询缓存,采用query.list()查询实体
代码如下所示。
~~~
/**
* 开启查询,开启二级缓存,采用query.list()查询实体
*
* 在两个session中发query.list()查询
*/
public voidtestCache6() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Liststudents = session.createQuery("select s from Student s")
.setCacheable(true)
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("-------------------------------------------------------");
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//不再发出查询语句,因为配置了二级缓存和查询缓存
Liststudents = session.createQuery("select s from Student s")
.setCacheable(true)
.list();
for (int i=0;i<students.size(); i++) {
Studentstudnet = (Student)students.get(i);
System.out.println(studnet.getName());
}
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
结果如下所示
Hibernate: select student0_.id as id0_,student0_.name as name0_, student0_.classesid as classesid0_ from t_studentstudent0_
只发出一次sql请求,当我们第一次执行query.list()会放到二级缓存中,和query缓存中。当我们第一次执行查询时,会找到相应的id到缓存中查找,在二级缓存中存在,则直接从二级缓存中取出数据,不再向数据库中发出sql语句。
### 八、查询缓存总结
查询缓存是缓存普通属性结果集的,对实体对象的结果集会缓存id。查询缓存的生命周期,当关联的表发生修改时,查询缓存的生命周期结束。
而开启缓存的时候,我们就要去维护缓存,如果缓存和内存中的数据不一致的话,和数据不同步,可能给用户显示的是脏数据了。所以根据需要使用缓存机制。
Hibernate旅程(八)Hibernate缓存机制–二级缓存
最后更新于:2022-04-01 14:50:02
Hibernate二级级缓存
上篇介绍了[Hibernate](http://blog.csdn.net/lovesummerforever/article/details/20997879)[一级缓存](http://blog.csdn.net/lovesummerforever/article/details/20997879),主要是session缓存,session生命周期结束,缓存也就结束。二级缓存相对于一级缓存来说是一个范围更广阔一些,就比你住的地方周围有多个小卖铺(session缓存),和一个大型超市,原料加工厂送货的时候送小卖铺一份的同时,必然送一份到超市。而给第二个小卖铺送一份的同时,也送给超市一份,这个超市就是我们的SessionFactory。hibernate二缓存的又称为“SessionFactory的缓存”缓存的生命周期和SessionFactory(线程安全,一个数据库对应一个,重量级)的生命周期一致,所以SessionFactory可以管理二级缓存。
下面来看session控制的二级缓存。
### 二级缓存配置
1、需要引入第三方的jar包,hibernate的Cglib.jar。
2、在缓存的配置文件来控制缓存,我们可以拷贝hibernate已有项目中的ehcache.xml配置文件到自己的项目中。通过这个文件,我们可以对二级缓存进行设置,例如缓存的时间,缓存的代销,缓存间隔多长时间自动被清掉,缓存超时间直接缓存到磁盘指定的位置上等设置。
3、在hibernate.cfg.xml文件中加入缓存产品提供商。
~~~
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
~~~
4、启用二级缓存,设置为true,默认是true,添加上有助于我们手动关闭和开启二级缓存。
~~~
<propertyname="hibernate.cache.use_second_level_cache">true</property>
~~~
5、指定哪些实体类使用二级缓存。
方法一:在对应实体的.hbm.xml文件中设置,<cacheusage="read-only"/>这样如果我们想要知道二百个映射文件哪些使用了二级缓存,就要查看二百个文件,所以我们可以把对实体加入二级缓存的策略放到hibernate.cfg.xml进行配置。也就是方法二。
方法二:处于好管理的目的我们把哪个实体使用二级缓存的配置放到hibernate.cfg.xml文件中。例如执行把student实体加入二级缓存策略。 <!--
指定Student使用二级缓存
-->
~~~
<class-cacheclass="com.bjpowernode.hibernate.Student"usage="read-only"/>
~~~
注意:缓存策略通常采用read-only和read-write
缓存原则;通常是读大于写的数据进行缓存。
### 开启二级缓存在两个session中使用两次load()进行查询
代码如下所示。
~~~
/**
* 开启二级缓存
*
* 在两个session中发load查询
*/
public voidtestCache1() {
Sessionsession = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
//不会发出查询语句,因为配置二级缓存,session可以共享二级缓存中的数据
//二级缓存是进程级的缓存
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
打印结果如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa7ec3a.jpg)
由此可知,虽然我们使用的是两个session,但第二次查询的时候没有再向数据库发出查询语句。也就是第一次查询的时候放入session的同时也放入SessionFactory缓存中一份,而第二个session再查询的时候,就直接从二级缓存中取出就可以了。
### 开启二级缓存在两个session中使用两次get()进行查询。
代码就是把上述代码load方法改动为get方法。
运行结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa7ec3a.jpg)
同load一样,对于sessionFactory级别的缓存,第二次查询时就不再向数据库发出查询sql命令。
### 开启二级缓存,在两个session中发出load查询,采用SessionFactory管理二级缓存
代码如下所示。
~~~
/**
* 开启二级缓存
*
* 在两个session中发load查询,采用SessionFactory管理二级缓存
*/
public voidtestCache3() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
//管理二级缓存 (evict清除的意思)
//HibernateUtils.getSessionFactory().evict(Student.class);
HibernateUtils.getSessionFactory().evict(Student.class, 1);
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
//会发出查询语句,因为二级缓存中的数据被清除了
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
显示结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa981c3.jpg)
根据显示结果可知,在两个session中发出load查询,第一次放入session缓存同时放入了二级缓存sessionFactory中。在第二个session中,首先使用SessionFactory对二级缓存管理,使用evict()方法清除二级缓存,所以第二个session采用load查询时,需要重新向数据库发送sql命令。
### 一级缓存和二级缓存交互
代码如下所示。
~~~
public voidtestCache4() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//禁止将一级缓存中的数据放到二级缓存中。缓存模式设置为忽略。
session.setCacheMode(CacheMode.IGNORE);
Studentstudent =(Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
//会发出查询语句,因为禁止了一级缓存和二级缓存的交互,一级缓存没有放到二级缓存中。
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
显示结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa981c3.jpg)
从上图可以看出,首先我们开启session的事务,然后设置了当写入session缓存中的同时不写入二级缓存,session.setCacheMode(CacheMode.IGNORE),这样第二个session再查询的时候就不能从二级缓存中找到相应的内容,仍需向数据库发出sql请求。
### 大批量数据的添加。
当添加或更新大批量数据的时候,为了防止缓存中数据量过大,我们可以设置删除session一级缓存,如果此时开启二级缓存,那么也会同样再二级缓存中存放一份,这样的话很可能会导致缓存的溢出,所以对于大数据来说,如果放入了一级缓存中,就应该禁止再放入二级缓存中。
代码如下所示。
~~~
/**
* 大批量的数据添加
*/
public voidtestCache5() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
//禁止一级缓存和二级缓存交互
session.setCacheMode(CacheMode.IGNORE);
for (int i=0;i<100; i++) {
Studentstudent = new Student();
student.setName("张三" +i);
//一级缓存放一份,二级缓存也放一份。
session.save(student);
//每20条更新一次
if (i %20 == 0) {
session.flush();
//清除缓存的内容
//只清除了一级缓存中的内容,没有清除一级缓存中的内容。如果数据量过大,一样溢出。
//大于大数据量来说,如果配置了一级缓存的话,就应该禁止再放入二级缓存中。
session.clear();
}
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
控制台打印sql如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa5c8c0.jpg)
### Hibernate二级缓存总结
从上可知,二级缓存时比一级缓存作用域更大,是在整个应用程序中。一级缓存是不能被卸载的,是必需的,不允许也无法卸载,它是事务范围的缓存。而二级缓存是可配置的,可卸载的,SessionFactory生命周期和应用程序整个过程对应,就有可能出现并发问题。
什么样的数据适合放到二级缓存中?
1、很少被修改的数据
2、不是很重要的,并允许出现偶尔的并发数据
3、不会被并发的数据
4、常量数据
什么样的数据适不合放到二级缓存中?
1、经常被修改的数据。
2、绝对不允许并发的数据。
3、与其他应用共享的数据。
Hibernate旅程(七)Hibernate缓存机制–一级缓存
最后更新于:2022-04-01 14:49:59
Hibernate一级缓存
缓存就是你去小卖铺买东西,不用再去生产车间里买东西,当小卖铺倒闭了,也就是session缓存生命周期结束。hibernate一级缓存的声明周期很短,和session的生命周期一致,hibernate的一级缓存也叫做session级缓存,或叫事务级缓存。下面来看session控制的一级缓存。
### 同一session中使用两次load()进行查询。
代码入下所示,我们在同一个session中两次调用load()。
~~~
/**
* 在同一个session中发出两次load查询
*/
public voidtestCache1() {
Sessionsession = null;
try {
//使用load查询两遍.
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent =(Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
//不会发出查询语句,load使用缓存,在同一个session中.
student =(Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
两次都采用load()进行加载并打印出学生的名字。发出的sql语句如下所示。对于loadlazy加载,只有在使用load加载上来的类,才真正的去数据库查询。控制台打印的sql代码如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908f9b4564.jpg)
从显示结果中我们可以看出,在第一次真正使用load的时候,发出sql语句。Hibernate会把查询上来真实的对象放到session的map中。当第二次load再次使用的时候,不会再发送sql语句,而是直接从session的缓存中取出。
### 同一session中使用两次get()进行查询。
源代码也就是把上述load换成get。
Get和load的区别,get不支持延迟加载而load支持延迟加载,但当我们在同一次访问中访问两次get方法时,可以看到当加载get方法的时候控制台立刻打印sql,同时放到了缓存中。当我们第一次调用get方法的时候,同样和load方法一样,没有再次去数据库中查询,而是直接从缓存中取出来显示。打印结果如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908f9c95a6.jpg)
### 在同一session中发出两次iterate查询,查询实体对象。
代码如下所示。
~~~
/**
* 在同一个session中发出两次iterate查询,查询实体对象
* 发出两次迭代查询.查询实体对象.
*/
public void testCache3() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Iteratoriter = session.createQuery("from Student s where s.id<5").iterate();
while(iter.hasNext()) {
Studentstudent = (Student)iter.next();
System.out.println(student.getName());
}
System.out.println("--------------------------------------");
//它会发出查询id的语句,但不会发出根据id查询学生的语句,因为iterate使用缓存
iter= session.createQuery("from Student s where s.id<5").iterate();
while(iter.hasNext()) {
Studentstudent = (Student)iter.next();
System.out.println(student.getName());
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
运行结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908f9e5afd.jpg)
第一次调用迭代器的查询时,首先发出查询id的语句,并根据id查询学生。当第二次调用时,只会发出查询id的语句,不会再根据id来查询对应的学生对象。这说明iterate(迭代器)是支持缓存的。
### 在同一session中发出两次iterate查询,查询实体对象。
代码如下所示。
~~~
Session session = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Iteratoriter = session.createQuery("select s.name from Student s wheres.id<5").iterate();
while(iter.hasNext()) {
Stringname = (String)iter.next();
System.out.println(name);
}
System.out.println("--------------------------------------");
//iterate查询普通属性,一级缓存不会缓存,所以发出查询语句
//一级缓存是缓存实体对象的
iter= session.createQuery("select s.name from Student s wheres.id<5").iterate();
while(iter.hasNext()) {
Stringname = (String)iter.next();
System.out.println(name);
}
session.getTransaction().commit();
~~~
显示结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa10478.jpg)
根据显示结果可知,迭代器查询普通属性,一级缓存不会存储,所以当第二次查询的时候仍然发出查询语句。这说明iterate一级缓存缓存的是实体对象,对于普通属性不会缓存。
在两个session中发出load查询。
代码如下所示。
~~~
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = (Student)session.load(Student.class, 1);
//会发出查询语句,session间不能共享一级缓存数据
//因为他会伴随着session的消亡而消亡
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
~~~
显示结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa2e6e2.jpg)
从上图可以看出,会发出两次sql,这也说明了session之间不能共享一级缓存数据,因为缓存会本随着自己的那个session的消亡而消亡。
### 在一个session中先调用save(),再调用get或load查询刚刚save的数据。
代码如下所示。
~~~
/**
* 在同一个session中先调用save,再调用load查询刚刚save的数据
*/
public voidtestCache6() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
Studentstudent = new Student();
student.setName("张三");
Serializableid = session.save(student);
student= (Student)session.load(Student.class,id);
//不会发出查询语句,因为save支持缓存
System.out.println("student.name=" +student.getName());
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
显示结果如下所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa46506.jpg)
从图中可以看出,只发送一次插入语句,当我们再次查询的时候,没有去数据库进行查询,这说明当使用session.save()时,已经放入缓存中。再进行查询时会从缓存中取出。
### 大批量数据的添加。
代码如下所示。
~~~
public voidtestCache7() {
Sessionsession = null;
try {
session= HibernateUtils.getSession();
session.beginTransaction();
for (int i=0;i<100; i++) {
Studentstudent = newStudent();
student.setName("张三" +i);
session.save(student);
//每20条更新一次
if (i %20 == 0) {
session.flush();
//清除缓存的内容
session.clear();
}
}
session.getTransaction().commit();
}catch(Exceptione) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
~~~
打印sql如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908fa5c8c0.jpg)
从图中可知,打印了100条inset语句,在一个session中缓存100条数据很大,我们可以设置每20条清空缓存。
### Hibernate一级缓存总结
从上可知,load、get、iterate查询实体对象时,支持一级缓存,但查询普通属性时不支持一级缓存,当我们大批量数据插入或更新时,由于缓存中数据量太大,我们可以设置缓存中的条数,使用session.clear()来清除缓存。
Hibernate旅程(六)Hibernate映射–继承映射
最后更新于:2022-04-01 14:49:57
上篇主要讲述了[hibernate的对象关系映射](http://blog.csdn.net/lovesummerforever/article/details/20901011)。本篇讲述对象继承关系是如何映射具体的表。
对于继承类映射到表有三种方式:
1、一棵继承树映射一张表。
2、每个子类各自映射表。
3、继承树的每个类各自映射表。
### 1、一棵继承树一张表。
直接上图,如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bd3c71f.jpg)
映射文件代码如下所示。
~~~
<hibernate-mapping package="com.bjpowernode.hibernate">
<class name="Animal" table="t_animal" lazy="false">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="type" type="string">
</discriminator>
<property name="name"/>
<property name="sex"/>
<subclass name="Pig" discriminator-value="P">
<property name="weight"/>
</subclass>
<subclass name="Bird" discriminator-value="B">
<property name="height"/>
</subclass>
</class>
</hibernate-mapping>
~~~
### 2、每个子类一张表。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908f94d034.jpg)
映射文件代码如下所示。
~~~
<!--加上package,所以不用写完整了.-->
<hibernate-mapping package="com.bjpowernode.hibernate">
<class name="Animal" table="t_animal" abstract="true">
<id name="id">
<generator class="assigned"/>
</id>
<property name="name"/>
<property name="sex"/>
<union-subclass name="Pig" table="t_pig">
<property name="weight"/>
</union-subclass>
<union-subclass name="Bird" table="t_bird">
<property name="height"/>
</union-subclass>
</class>
</hibernate-mapping>
~~~
### 3、每个类一张表(子类+父类)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908f9661c0.jpg)
代码如下所示。
~~~
<hibernate-mapping package="com.bjpowernode.hibernate">
<class name="Animal" table="t_animal" lazy="false">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="sex"/>
<joined-subclass name="Pig" table="t_pig">
<!-- 将会在pig表中加上一个pid -->
<key column="pid"/>
<property name="weight"/>
</joined-subclass>
<joined-subclass name="Bird" table="t_bird">
<!-- 将会在pig表中加上一个pid -->
<key column="bid"/>
<property name="height"/>
</joined-subclass>
</class>
</hibernate-mapping>
~~~
以上三种策略,各有优缺点:
单表继承。查询速度最快,效率高,但是存在冗余字段。
类表继承。层次清楚,无冗余;但是如果使用自增主键方式会产生重复主键。需要使用手动分配主键。
具体表继承。层次最明了,这是优点,也是缺点,而且数据操作效率不高!
Hibernate旅程(五)Hibernate映射–基本类映射和对象关系映射
最后更新于:2022-04-01 14:49:55
回想一些我们在没有学习ssh的时候,我们建立数据库的表时,首先是数据库建模E-R图,然后再通过实体模型来建立关系模型,再建立相应的表。实体间存在三种关系,一对一,一对多(或者说多对一),多对多。而如今我们要根据类来映射相应的表,那只能是通过类与类之间的关系加上映射文件来映射数据库的表。我们学习UML建模,类与类之间存在五种关系,继承,实现,关联,依赖,聚合/组合,在hibernate中实体类之间的关系也是如此,对于不同的关系对应的代码实现我们已经很熟悉了,所以对于实体类是复习的知识。
Hibernate的本质就是对象关系映射(ObjectRelational Mapping),ORM实现了将对象数据保存到数据库中,以前我们对关系表进行操作,执行增删改查等任务,现在我们不再对关系表进行操作,而是直接对对象操作。hibernate中的ORM映射文件通常以.hbm.xml作为后缀。使用这个映射文件不仅易读,而且可以手工修改,也可以通过一些工具来生成映射文档。下面将对hibernate中的映射进行介绍。
Hibernate映射分类,如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc196c1.jpg)
### 1 基本类映射
根据实体类创建相应的表,这种简单的关系为hibernate基本映射。
User1实体类代码如下:
~~~
//user实体。
public classUser1 {
//用户编号。
private String id;
//名字。
private String name;
//密码。
private String password;
//创建日期。
private Date createTime;
//失效时间。
private Date expireTime;
public String getId() {
return id;
}
// publicvoid setId(String id) {
// this.id= id;
// }
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(Stringpassword) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(DatecreateTime) {
this.createTime = createTime;
}
public Date getExpireTime() {
return expireTime;
}
public void setExpireTime(DateexpireTime) {
this.expireTime = expireTime;
}
}
~~~
User1.hbm.xml映射文件如下所示:
~~~
<hibernate-mapping package="com.bjpowernode.hibernate">
<class name="User1" table="t_user1">
<id name="id"column="user_id" length="32"access="field">
<generator class="uuid" />
</id>
<!-- 设置主键不能重复和不能为空的属性. -->
<property name="name" length="30"unique="true" not-null="true"/>
<property name="password"/>
<property name="createTime" type="date" column="create_time"/>
<property name="expireTime"/>
</class>
</hibernate-mapping>
~~~
通过User1.hbm.xml映射文件将User1对象转换为关系数据库中的表t_user1。
转换出的结果如下所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc3a042.jpg)
### 2 对象关系映射
### 2.1 多对一关联映射(单向)
例如用户和组的关系就是多对一的关系,多个用户对应一个组。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc50e52.jpg)
将实体映射成表,将对应的实体映射成表。对应的属性映射成表字段。
多对一关联映射是在多的一端来维护关联字段,在我们这个例子中也就是在用户一端来维护关系字段。
User.hbm.xml文件。
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.User" table="t_user" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<many-to-one name="group" column="groupid"cascade="save-update"></many-to-one>
</class>
</hibernate-mapping>
~~~
Group.hbm.xml文件。
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.Group" table="t_group">
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
</class>
</hibernate-mapping>
~~~
在这里我们看的代码就看*.hbm.mlx代码,因为对于类之间的关联,在实现时,一个类作为另一个类的私有成员,这一点在学UML建模的时候我们都懂了,在这里主要看的是ORM的M,也就是*.hbm.xml文件。
### 2.2 一对一关联映射
一对一关联映射在实际生活中是比较常见的,如人与家庭住址的关系,通过人这个对象可以找到他家庭住址相关的内容。
####2.2.1 一对一映射(单向主键关联)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc63594.jpg)
单向一对一主键关联,靠的是它们的主键相等,从Person中能看到IdCard,也就是把t_idCard中的主键拿过来当做t_Pseron的主键。
Xml文件中:
~~~
<class name="com.bjpowernode.hibernate.Person"table="t_person" >
<id name="id">
<!-- 采用foreign生成策略,foreign会取得关联对象的标识 -->
<generator class="foreign" >
<!--property指的是关联对象。 -->
<param name="property">idCard</param>
</generator>
</id>
<property name="name"/>
<!-- 一对一关联映射,主键关联. -->
<!--
one-to-one标签指示hibernate如何加载其关联对象,默认根据主键加载.
也就是拿到关系字段值,根据对端的主键来加载关联对象.
constrained="true",表示当前主键(Person的主键)还是一个外键 .
参照了对端的主键(IdCard的主键),也就是会生成外键约束语句.
-->
<one-to-one name="idCard" constrained="true"/>
</class>
~~~
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.IdCard" table="t_idCard" >
<id name="id">
<generator class="native" />
</id>
<property name="cardNo"/>
</class>
</hibernate-mapping>
~~~
一对一的关系是通过one-to-one元素定义的。
#### 2.2.2 一对一映射(双向主键关联)
一对一双向主键关联与一对一单向主键关联的区别就是,一对一单向主键关联,在person端能看到idCard,而idCard不能看到Person端。而双向关联就是在idCard端也能看到person,也就是不但在Person.hbm.xml中加上<one-to-one>标签,同时在IdCard.hbm.xml文件中加上<one-to-one>标签。代码如下所示。
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.IdCard" table="t_idCard" >
<id name="id">
<generator class="native" />
</id>
<property name="cardNo"/>
<one-to-one name="person"/>
</class>
</hibernate-mapping>
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc7a925.jpg)
#### 2.2.3 一对一映射(单向唯一外键关联)
一对一单向唯一外键关联,也就是多对一关联的特例,把多的一端限制为一,就是一对一唯一外键关联。同多对一一样,在一端加入另一端的并采用<many-to-one>标签,通过unique="true",这样来限制了多的一端为一。
先上代码。
IdCard.hbm.xml
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.IdCard" table="t_idCard" >
<id name="id">
<generator class="native" />
</id>
<property name="cardNo"/>
</class>
</hibernate-mapping>
~~~
Person.hbm.xml
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.Person" table="t_person" >
<id name="id">
<!-- 采用foreign生成策略,foreign会取得关联对象的标识 -->
<generator class="native" />
</id>
<property name="name"/>
<many-to-one name="idCard" unique="true"></many-to-one>
</class>
</hibernate-mapping>
~~~
图如下所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc8ea7d.jpg)
在t_pserson端加上一个外键字段idCard,限制idCard的唯一性就是一对一唯一外键关联。
####2.2.4 一对一映射(双向唯一外键关联)
一对一唯一外键单向关联我们已经了解了,双向反过来就是在没有的一端加上就可以了。
我们的IdCard.hbm.xml中采用<one-to-one>标签。
~~~
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.IdCard" table="t_idCard" >
<id name="id">
<generator class="native" />
</id>
<property name="cardNo"/>
<one-to-one name="person" property-ref="idCard"></one-to-one>
</class>
</hibernate-mapping>
~~~
而person.hbm.xml同一对一唯一外键单向关联一样。
~~~
<class name="com.bjpowernode.hibernate.Person" table="t_person" >
<id name="id">
<!-- 采用foreign生成策略,foreign会取得关联对象的标识 -->
<generator class="native" />
</id>
<property name="name"/>
<many-to-one name="idCard" unique="true"></many-to-one>
</class>
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bca1690.jpg)
从上述中可以总结出,对于一对一关联映射,主键关联和唯一外键关联单向和双向产生出的表结构是一样的,不同的是在加载的时候不同。也就是一对一双向关联和一对一单向关联的相比,只是改变了一对一关联映射的加载,而没有改变存储。
### 2.3 一对多关联映射
#### 2.3.1 一对多关联映射(单向)
上面我们介绍了多对一,我们反过来看一对多不就是多对一吗?那还用再进行不同的映射吗?有什么差别吗?一对多和多对一映射原理是一致的,存储是相同的,也就是生成的数据库的表是一样的,他们之间不同的是维护的关系不同。
他们之间不同点是维护的关系不同
多对一维护的关系是:多指向一的关系,有了此关系,加载多的时候可以将一加载上来。
一对多维护的关系是:一指向多的关系,有了此关系,在加载一的时候可以将多加载上来。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bcb6e31.jpg)
代码如下所示。
Class.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.Classes" table="t_Classes" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<set name="students">
<!--
<keycolumn="classesid" not-null="true"/>
-->
<key column="classesid" />
<one-to-many class="com.bjpowernode.hibernate.Student"/>
</set>
</class>
~~~
Students.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.Student" table="t_student" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
</class>
~~~
从班级能看到学生,是班级来维护关系,不是学生来维护关系,学生不知道自己是哪个班,所以在存储学生的时候,班级的代码不知道。为了更新学生是哪个班级的要发出很多update语句来告诉学生是哪个班级的。当我们设置classesid not-null=“true”时,则将无法保存数据,解决办法我们改为双向关联映射。
#### 2.3.2 一对多关联映射(双向)
为了解决一对多单向可能存在的问题,我们采用双向一对多,每一方都能维护对方。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bcd50e5.jpg)
一对多双向关联映射方式:
在一的一端的集合上采用<key>标签,在多的一端加入一个外键。
在多的一端采用<many-to-one>的标签
!~注意<key>标签和<many-to-one>标签加入字段保持一致,否则会产生数据混乱。
代码如下所示。
~~~
<class name="com.bjpowernode.hibernate.Classes" table="t_Classes" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<set name="students" inverse="true">
<!--
<keycolumn="classesid" not-null="true"/>
-->
<key column="classesid" />
<one-to-many class="com.bjpowernode.hibernate.Student"/>
</set>
</class>
~~~
~~~
<class name="com.bjpowernode.hibernate.Student" table="t_student" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<many-to-one name="classes"column="classesid"/>
</class>
~~~
注意:Inverse属性
1、Inverse中文意思为相反的,反转。在hibernate中inverse可以用在一对多和多对多双向关联上,inverse默认是false,为false的时候表示本端可以维护关系,如果inverse为true,则本端不能维护关系,会交给另一端维护关系,本端失效,所以在一对多关联映射我们通常在多的一端维护关系,让一的一端失效。
2、Inverse是控制方向上的反转,只影响存储。
比较一对多单向和双向映射,从存储结构上看没有什么区别,但是从配置文件上看,一对多双向比一对多单向,一对多双向关联的配置文件中在多的一端的配置文件上存在<many-to-one>相关配置,即保证多对一的映射。
### 2.4 多对多关联映射
#### 2.4.1 多对多关联映射(单向)
多对多对象关系映射,需要加入一张新表完成基本映射。如下图所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bcf3b65.jpg)
代码。
Role.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.Role" table="t_role">
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
</class>
~~~
User.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.User" table="t_user" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<set name="roles" table="t_user_role">
<key column="user_id"/>
<many-to-many class="com.bjpowernode.hibernate.Role" column="role_id"/>
</set>
</class>
~~~
#### 2.4.2 多对多关联映射(双向)
双向多对多对象关系映射,是两端都能将对方加载上来,双向都需要加上标签映射。
要注意:
生成中间表名必须一样
生成中间表字段必须一样
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bd180b3.jpg)
代码如下所示。
Role.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.Role" table="t_role">
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<set name="users" table="t_user_role">
<key column="role_id"/>
<many-to-many class="com.bjpowernode.hibernate.User" column="user_id"/>
</set>
</class>
~~~
.User.hbm.xml
~~~
<class name="com.bjpowernode.hibernate.User"table="t_user" >
<id name="id">
<generator class="native" />
</id>
<property name="name"/>
<set name="roles" table="t_user_role">
<key column="user_id"/>
<many-to-many class="com.bjpowernode.hibernate.Role" column="role_id"/>
</set>
</class>
~~~
**区别**:单向多对多和双向多对多存储结构没有任何的区别,但他们的映射文件是有区别的,加载过程是不同的。
### 3关系映射总结
综上所述,可以看出,同一类映射,无论是单向还是双向,他们的存储结构是相同的,之所以映射文件不同,是因为加载时不同(在增删改时)。
无论是多对一、一对多、一对一还是多对一,A对B,A就是主动方,A主动想要了解B的情况,这样把B设置到A端。而双向,也就是A对B,A想了解B的信息,而B也想了解A的信息,那就要同时把A设置到B端了。
Hibernate旅程(四)Hibernate对数据库删除、查找、更新操作
最后更新于:2022-04-01 14:49:52
上篇,我们以向数据库添加操作来演示[hibernate](http://blog.csdn.net/lovesummerforever/article/details/19171571)[持久化对象的三种状态](http://blog.csdn.net/lovesummerforever/article/details/19171571)。本节继续hibernate对数据库的其他操作,删除、查询、修改。
Hibernate对数据删除操作
删除User表中个一条数据,是需要更具User表的主键id值来删除的。首先根据id值向数据库中查询出来对应的对象。可以采用两种方式一是session的get方法,一个是session的load方法。
Session的Get方法:调用这个方法会返回一个Object对象。然后我们对其强制转换。Useruser = (User)session.get(User.class,” 402881e5441c035e01441c0360510003”); 当我们传递id值在数据中查找没有相应的结果时,get方法会返回一个null值。
区别:get方法加载的时候会立刻发出sql语句去查询,而load方法在执行的时候没有立刻的发出sql去查询,生成一个代理User,没有生成真正的User。当我们真正的去用这个user的时候才会加载真正的User。Load()支持延迟加载,而Get()不支持延迟加载。Get加载的对象不存在时返回的是null对象,而Load()加载对象不存在时会抛出ObjectNotFoundException异常。
Session的Load方法:同样是调用这个方法返回一个Object对象,再进行强制转换。
然后我们通过get或load加载上来对应user表id的对象,再调用session的delete方法删除该对象同时删除表中的一条记录,代码如下所示。
第一种删除方式。
~~~
**public**void**testDel1()
{
Sessionsession =**null**;
**try**
{
session= HibernateUtils.*getSession*();
//开启事务.
session.beginTransaction();
//采用load查询不存在的数据,hibernate会抛出object not found exception
Useruser = (User)session.load(User.**class**,"402881e5441c035e01441c0360510003");
//删除表中的记录.
//删除,建议用此种方式删除,先加载再删除.
session.delete(user);
//提交事务.把内存的改变提交到数据库上.
session.getTransaction().commit();
}**catch**(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
第二种删除方式,手动构造detached对象再删除。代码如下所示。
~~~
//测试方法以test开头.测试del方法.返回存在的加载的.
**public**void**testDel2()
{
Sessionsession =**null**;
**try**
{
session= HibernateUtils.*getSession*();
//开启事务.
session.beginTransaction();
//手动构造的Detached对象.
Useruser =**new**User();
user.setId("402881e4441b3d1c01441b3f5dfe0001");
session.delete(user);
//提交事务.把内存的改变提交到数据库上.
session.getTransaction().commit();
}**catch**(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
Hibernate对数据查询操作
一般查询,代码如下所示。
//查询方法.
~~~
**public**void**testQuery1()
{
Sessionsession =**null**;
**try**
{
session= HibernateUtils.*getSession*();
session.beginTransaction();
//参数是一个字符串,是HQL的查询语句.注意此时的的UserU为大写,为对象的,而不是表的.
Queryquery = session.createQuery("from User");
//使用List方法.
ListuserList = query.list();
//迭代器去迭代.
**for**(Iteratoriter=userList.iterator();iter.hasNext();)
{
Useruser =(User)iter.next();
System.*out*.println("id="+user.getId() + "name="+user.getName());
}
session.getTransaction().commit();
}**catch**(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
分页查询,代码如下所示。
//分页查询,从什么地方查,查几个;
~~~
**public**void**testQuery2()
{
Sessionsession =**null**;
**try**
{
session=HibernateUtils.*getSession*();
session.beginTransaction();
//参数是一个字符串,是HQL的查询语句.注意此时的的UserU为大写,为对象的,而不是表的.
Queryquery = session.createQuery("from User");
//从第一个开始查起.可以设置从第几个查起.
query.setFirstResult(0);
//最大条数为两个
query.setMaxResults(2);
//使用List方法.
ListuserList = query.list();
//迭代器去迭代.
**for**(Iteratoriter=userList.iterator();iter.hasNext();)
{
Useruser =(User)iter.next();
System.*out*.println("id="+user.getId() + "name="+user.getName());
}
session.getTransaction().commit();
}**catch**(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
Hibernate对数据更新操作
手动构造detached对象,调用session的update()方法,代码如下所示。
~~~
//测试方法以test开头.测试update方法.返回存在的加载的.
**public**void**testUpdate1()
{
Sessionsession =**null**;
**try**
{
session= HibernateUtils.*getSession*();
//开启事务.
session.beginTransaction();
//采用load查询不存在的数据,hibernate会抛出object not found exception
//手动构造的Detached对象.
Useruser =**new**User();
user.setId("402881e5441bfb0601441bfb075b0002");
user.setName("周六");
session.update(user);
//提交事务.把内存的改变提交到数据库上.
session.getTransaction().commit();
}**catch**(Exception e){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
加载对象,调用session的update()方法,让对象处于持久化状态的时候进行更新操作,代码如下所示。
~~~
//测试方法以test开头.测试update方法.返回存在的加载的.
**public**void**testUpdate2()
{
Sessionsession =**null**;
**try**
{
session= HibernateUtils.*getSession*();
//开启事务.
session.beginTransaction();
//采用load查询不存在的数据,hibernate会抛出object not found exception
//先把要更新的查出来.
//建议采用此种方式,先加载再更新的方式.
Useruser = (User)session.load(User.**class**,"402881e5441bfb0601441bfb075b0002");
//查出来的话就直接放入了.处于持久化状态.
user.setName("周日");
//显示的调用,因为为持久化状态也可以不显示调用.
session.update(user);
//提交事务.把内存的改变提交到数据库上.
session.getTransaction().commit();
}**catch**(Exceptione){
e.printStackTrace();
session.getTransaction().rollback();
}**finally**{
HibernateUtils.*closeSession*(session);
}
}
~~~
Hibernate旅程(三)Hibernate持久化对象的三个状态
最后更新于:2022-04-01 14:49:50
Hibernate中的对象有3中状态,瞬时对象(TransientObjects)、持久化对象(PersistentObjects)和离线对象(DetachedObjects也叫做脱管对象)。
下图3.1显示了瞬时对象、持久化对象和离线对象之间的关系以及它们之间的转换。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bb87371.jpg)
图3.1
**临时状态**:由java的new命令开辟内存空间的java对象也就是普通的java对象,如果没有变量引用它它将会被JVM收回。临时对象在内存中是孤立存在的,它的意义是携带信息载体,不和数据库中的数据由任何的关联。通过Session的save()方法和saveOrUpdate()方法可以把一个临时对象和数据库相关联,并把临时对象携带的信息通过配置文件所做的映射插入数据库中,这个临时对象就成为持久化对象。
**持久化状态**:持久化对象在数据库中有相应的记录,持久化对象可以是刚被保存的,或者刚被加载的,但都是在相关联的session声明周期中保存这个状态。如果是直接数据库查询所返回的数据对象,则这些对象和数据库中的字段相关联,具有相同的id,它们马上变成持久化对象。如果一个临时对象被持久化对象引用,也立马变为持久化对象。
如果使用delete()方法,持久化对象变为临时对象,并且删除数据库中相应的记录,这个对象不再与数据库有任何的联系。
持久化对象总是与Session和Transaction关联在一起,在一个session中,对持久化对象的操作不会立即写到数据库,只有当Transaction(事务)结束时,才真正的对数据库更新,从而完成持久化对象和数据库的同步。在同步之前的持久化对象成为脏对象。
当一个session()执行close()、clear()、或evict()之后,持久化对象就变为离线对象,这时对象的id虽然拥有数据库的识别值,但已经不在Hibernate持久层的管理下,他和临时对象基本上一样的,只不过比临时对象多了数据库标识id。没有任何变量引用时,jvm将其回收。
**脱管状态**:Session关闭之后,与此Session关联的持久化对象就变成为脱管对象,可以继续对这个对象进行修改,如果脱管对象被重新关联到某个新的Session上,会在此转成持久对象。
脱管对象虽然拥有用户的标识id,所以通过update()、saveOrUpdate()等方法,再次与持久层关联。
下面我们就通过使用hibernate,实现对数据库的增删改查来体现三种状态之间的转换过程。
**添加修改演示三种状态之间的变化**
当我们建立Session都要实例化SessionFactory,所以我们把重复的代码进行封装,并且session是单线程的。我们把对session的管理,打开session,关闭session等封装到工具类中,代码如下所示。
~~~
package com.bjpowernode.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
private static SessionFactory factory;
//static只初始化一次.
static
{
try{
//默认读取的是hibernate.cfg.xml 文件.
Configuration cfg = new Configuration().configure();
//建立SessionFactory.
factory = cfg.buildSessionFactory();
}catch(Exception e )
{
e.printStackTrace();
}
}
public static Session getSession()
{
//打开session.
return factory.openSession();
}
//关闭session.
public static void closeSession(Session session)
{
//判断是否为空.
//判断是否是打开状态再进行关闭.
if(session!=null)
{
if(session.isOpen())
{
session.close();
}
}
}
//返回工厂类.
public static SessionFactory getSessionFactory()
{
return factory;
}
}
~~~
Hibernate.cfg.xml代码如下所示。
~~~
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory >
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/Hibernate_session</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<mapping resource="com/bjpowernode/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
~~~
上一篇,我们把对表添加的操作放到普通的java类中,在这个类的main()方法中执行,如果我们再对表进行其他的操作呢?那是不是还要建立新的java类,多个方法就不容易测试了。我们使用测试工具类JUnit来做测试,来测试增删改查。首先建立源目录,在test包中放测试程序。
我们建立我们的测试程序SessionTest.java,继承TestCase类,这样我们在SessionTest.java类中测试数据库中的某个方法,方法名的规范要以test开头。我们向User表中添加一条记录如下代码所示。
~~~
package com.bjpowernode.hibernate;
import java.util.Date;
import junit.framework.TestCase;
import org.hibernate.Session;
import org.hibernate.Transaction;
public class SessionTest extends TestCase {
//测试方法以test开头.
public void testSave1()
{
Session session = null;
Transaction tx = null;
try
{
//取得session.
session = HibernateUtils.getSession();
//自己开启事务. 返回 transient的一个实例.
tx = session.beginTransaction();
//传入值.变为Transient状态.
User user = new User();
user.setName("张三");
user.setPassword("123");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
//进行保存.执行save则对session进行管理了. 处于持久状态.
//persistent状态的对象.当对象的属性发生改变的时候,hibernate在清理
//缓存的时候(脏数据检查)的时候,会和数据库同步.
session.save(user);
user.setName("李四");
//再次提交.
tx.commit();
}catch(Exception e)
{
e.printStackTrace();
if(tx!=null)
{
// 事务回滚.
tx.rollback();
}
}finally
{
//关闭session.当关闭session时处于Detached状态.
HibernateUtils.closeSession(session);
}
}
~~~
首先是建立对象与表的会话session,开启事务session.beginTransaction(),实例化User对象,当我们User user = new User()的时候,当我们new一个对象的时候数据库是没有的,所以是Transient对象(临时对象),然后给user赋值,设置名称和密码以及其他属性。 为对象的所有属性赋值完毕,接下来保存到会话session中,拿到session执行save(user)方法。 当我们执行session的save()方法时,这个对象就被session管理了,session中有个map,会把对象放到map中,此时的对象我们就成为persistent状态(持久状态)。
接下来我们又把user中的name属性设置为“李四”,之后提交事务。我们先再会话中存储的“张三”,之后改为“李四”。try catch来扑捉异常,当执行完毕,关闭session后,对象处于detached状态(离线状态)。
我们创建数据库,利用ExportDB.java方法建立表。之后执行SessionTest的testSave1()方法,当执行到session方法的时候,表中自动生成user表的id值,并且名子为“张三”,之后再次执行,名字又变为“李四”,之后执行事务的commit()方法tx .commit ,此时控制台才发出语句,如下图3.2。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bba0ace.jpg)
从控制台的语句中可以看出,显示发送的插入sql语句,后是update语句,首先是持久化对象user中的名字为“张三”,所以save的时候生成inset语句。此时user处于持久状态的对象,我们之后又给变了持久化对象,所以发送了一个修改语句。也就是当持久化对象发生修改时,我们再提交事务,就会把修改的全部体现出来(update语句)。
也就是我们再提交事务的时候,在清理缓存,也就是脏数据检查(内存中变了,而数据没变),要检查哪些数据是有问题的,要保持内存和数据库的同步。所以我们数据库中添加的记录,user的名字为李四(如图3.3所示)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bbb933e.jpg)
图3.3
如果上述代码中,我们在修改名字为李四后user.setName("李四");我们显示调用session的update()方法,session.update(),运行,会看到控制台上打印的sql语句和我们不加如session.update()打印的相同。持久化对象只要更改了,在提交事务的时候就会同步,没有必要再显示调用。
Detached状态演示
我们在执行完所有的操作,关闭session后,此时的user对象变为detached状态,此时进行操作。
代码如下所示。
~~~
public void testSave3()
{
Session session = null;
Transaction tx = null;
User user = null;
try
{
//取得session.
session = HibernateUtils.getSession();
//自己开启事务. fanhui transient的一个实例.
tx = session.beginTransaction();
//传入值.变为Transient状态.
user = new User();
user.setName("张三");
user.setPassword("123");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
//进行保存.执行save则对session进行管理了. 处于持久状态.
//persistent状态的对象.当对象的属性发生改变的时候,hibernate在清理
//缓存的时候(脏数据检查)的时候,会和数据库同步.
session.save(user);
user.setName("李四");
//可以显示的调用update方法,因为此时为持久状态,调用update没有什么意义.
//再次提交.
tx.commit();
}catch(Exception e)
{
e.printStackTrace();
if(tx!=null)
{
// 事务回滚.
tx.rollback();
}
}finally
{
//关闭session.当关闭session时处于Detached状态.
HibernateUtils.closeSession(session);
}
//已经不能用以前的session了.
user.setName("王五");
try
{
//得到新的session.
session = HibernateUtils.getSession();
//开启事务.
session.beginTransaction();
//将detached状态的对象重新纳入session管理.
//此时将变为persistent状态的对象.
//persistent状态的对象,在清理缓存时,会根数据库同步.
session.update(user);
//提交事务.把内存的改变提交到数据库上.
session.getTransaction().commit();
}catch(Exception e)
{
e.printStackTrace();
session.getTransaction().rollback();
}finally
{
HibernateUtils.closeSession(session);
}
}
~~~
取得detached状态的user对象,改变这个对象的name值,user.setName("王五");之后我们再new一个新的session,通过session开启事务,之后更新操作,session.update(user),也就是把离线的对象(或脱管对象)再纳入session管理,这样就会和数据库同步,因为session.update()就把user对象纳入session管理,user对象由离线状态变为persistent状态。
提交事务,将和数据库同步。把内存的改变体现到数据库上。控制台sql语句以及运行向表中添加记录结果如图3.4,3.5所示。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bbd328d.jpg)
图3.4
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bc0069e.jpg)
图3.5
本篇到此结束,下一篇[Hibernate对数据库删除、查找操作](http://blog.csdn.net/lovesummerforever/article/details/19190435)。
Hibernate旅程(二)Hibernate实例
最后更新于:2022-04-01 14:49:48
上篇大概的介绍了Hibernate框架,本篇一个简单的hibernate例子来学习。
### Hibernate配置
(1) 创建自己的java项目。
(2) 向自己的项目中添加Hibernate的相关的jar,我们可以创建自己的UserLibrary,这样就直接加入这个依赖包就可以了(如图2.1图2.2)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bb22926.jpg)
图2.1
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bb4330c.jpg)
图2.2
需要添加上的jar包包括,a、加入hibernate-3.2/lib/*.jar b、加入hibernate-3.2/hibernate2.jar c、加入数据库驱动(mysql驱动可以去官网上下载)这样添加到我们自定义的依赖包中,再在程序中引入这个包就可以了。
(3)加入hibernate配置文件,在学struts的时候我们常用的配置文件是struts-config.xml,hibernate的配置文件hibernate.cfg.xml,我们可以在hibernate-3.2\etc\hibernate.cfg.xml目录下拷贝到我们项目中的src下。
这样以上步骤就完成了几本hibernate的基本配置。
(4)接下来我们要建立我们的实体类了,以便完成对象与表的映射。建立User实体类,完成实体类的映射User.hbm.xml。代码如下所示。
User类代码:
~~~
package com.bjpowernode.hibernate;
import java.util.Date;
//user实体。
public class User {
//用户编号。
private String id;
//名字。
private String name;
//密码。
private String password;
//创建日期。
private Date createTime;
//失效时间。
private Date expireTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getExpireTime() {
return expireTime;
}
public void setExpireTime(Date expireTime) {
this.expireTime = expireTime;
}
}
~~~
User.hbm.xml代码如下所示:
~~~
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.auction">
<class name="com.bjpowernode.hibernate.User" >
<id name="id">
<generator class="uuid" />
</id>
<property name="name"/>
<property name="password"/>
<property name="createTime"/>
<property name="expireTime"/>
</class>
</hibernate-mapping>
~~~
(5) 将User.hbm.xml文件加入到hibernate.cfg.xml文件中,代码如下所示。
~~~
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="foo">
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/Hibernate_first</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<!--<property name="hibernate.format_sql">true</property> -->
<mapping resource="com/bjpowernode/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
~~~
(6) 编写工具类,ExportDB.java生成数据库中的表,代码如下所示。
~~~
package com.bjpowernode.hibernate;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
/**
* 将hbm生成ddl.
* @author summer
*
*/
public class ExportDB {
/**将hbm生成ddl.
* @param args
*/
public static void main(String[] args) {
//使用hibernate相关类configuation.
//默认读取hibernate.cfg.xml文件.
Configuration cfg = new Configuration().configure();
//接受配置文件.
SchemaExport export = new SchemaExport(cfg);
//打印到控制台,输出到数据库.
export.create(true, true);
}
}
~~~
(7) 建立客户端类Client,向表User中添加数据。代码如下所示。
~~~
package com.bjpowernode.hibernate;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/**
* 客户端向表中添加数据.
* @author summer
*
*/
public class client {
public static void main(String[] args) {
//默认读取的是hibernate.cfg.xml 文件.
Configuration cfg = new Configuration().configure();
//建立SessionFactory.
SessionFactory factroy = cfg.buildSessionFactory();
//取得session.
Session session = null;
try
{
//通过工厂取得session
session = factroy.openSession();
//开启事务.
session.beginTransaction();
//给对象赋值.
User user = new User();
user.setName("张三");
user.setPassword("123");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
//保存user对象到数据库.
// 一个实体类对象对应一个数据库中的表.
session.save(user);
//先拿到前面事务的对象.再提交事务.
session.getTransaction().commit();
}catch( Exception e)
{
e.printStackTrace();
//回滚事务.
session.getTransaction().rollback();
}finally{
if(session!=null)
{
if(session.isOpen())
{
//关闭session.
//hibernate中已经将connection的的关闭封装了.
//我们没有看到任何一条sql语句.
session.close();
}
}
}
}
}
~~~
(8) 加入log4j配置文件,将该配置文件拷贝到我们自己项目的src下,便于调试和跟踪。
(9)建立自己的数据库,执行ExportDB.java生成数据库中的表,执行client.java完成了对象与关系数据库表的对应,如下图3.2,建立的User表。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bb60df1.jpg)
图3.2
### Configuration类
上述代码中,Configuration类负责管理Hibernate的配置信息,一个Configuration类的实例代表了应用程序中java类到数据库映射的集合。我们创建一个Configuration实例,并通过这个实例创建SessionFactory实例。 //默认读取的是hibernate.cfg.xml文件.
Configurationcfg = new Configuration().configure();
//建立SessionFactory.
SessionFactoryfactroy = cfg.buildSessionFactory();
在这里默认读取的是hibernate.cfg.xml文件,在新建一个Configuration的实例时,Hibernate会在类路径中查找hibernate.properties文件和hibernate.cfg.xml文件,如果两个文件同时存在,则hibernate.cfg.xml将覆盖hibernate.properties文件。如果两个文件都不存在则抛出异常。我们也可以指定配置文件的路径,不用系统默认的hibernate.cfg.xml文件:Stringfilename= “my_hibernate_cfg.xml” Configuration config = new Configuration().configure(filename);
### Hibernate配置文件
从配置文件中可以看出,这个配置文件描述的是数据库的信息,如数据库看的URL、用户名、密码,hibernate使用方言(那种数据库),同时还可以管理数据库中各个表的映射文件,还可以配置事务策略等。
### SessionFactory类
SessionFactory负责Session实例的创建,为了创建一个SessionFactory对象,必须在Hibernate初始化的时候创建一个Configuration类的实例,并将已经写好的映射文件交给它处理。这样Configuration对象就可以创建一个SessionFactory对象,当SessionFactory对象创建成功后,Configuration对象就没用过了。SessionFactory是线程安全的,可以被多个单线程调用以取得session对象,而构造SessionFactory要消耗资源,所以多数情况下一个应用中只初始化一个SessionFactory,在这里我们可以考虑使用静态方法,来解决这个问题。在上述代码的基础上我们抽取出一个工具类,用来专门实例化工厂管理session,并且采用static每次只初始化一次,代码如下所示。
~~~
package com.bjpowernode.hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
private static SessionFactory factory;
//static只初始化一次.
static
{
try{
//默认读取的是hibernate.cfg.xml 文件.
Configuration cfg = new Configuration().configure();
//建立SessionFactory.
factory = cfg.buildSessionFactory();
}catch(Exception e )
{
e.printStackTrace();
}
}
public static Session getSession()
{
//打开session.
return factory.openSession();
}
//关闭session.
public static void closeSession(Session session)
{
//判断是否为空.
//判断是否是打开状态再进行关闭.
if(session!=null)
{
if(session.isOpen())
{
session.close();
}
}
}
//返回工厂类.
public static SessionFactory getSessionFactory()
{
return factory;
}
}
~~~
### Session
Session是Hibernate运作的核心,对象的声明周期、事务的管理以及数据库的存取都与它息息相关。Session是由SessionFactory创建的,SessionFactory是线程安全的,而Session不是线程安全的,所以让多个线程共享一个session,会引起冲突。
下一篇[Hibernate](http://blog.csdn.net/lovesummerforever/article/details/19171571)[持久化对象的三个状态](http://blog.csdn.net/lovesummerforever/article/details/19171571)。
Hibernate旅程(一)Hibernate架构概述
最后更新于:2022-04-01 14:49:46
### Hibernate引出
曾学过三层架构,三层分别为界面层、业务逻辑层、数据持久层。在javaweb中,界面层和业务逻辑层可以使用struts框架来封装一些常用的操作。而在我们的数据持久层中,对数据库的增删改查,我们经常要手动的控制事务,各种sql语句查询,各种参数传递,无论是对哪个表操作使用的均是同一种思路进行操作的。既然是同一种思路,那就是重复的东西了。而且我们还要和关系型数据库打交道,而不单单的是对象。而hibernate框架对此作了完美的封装。
### Hibernate简介
hibernate是一个基于java的对象/关系数据库映射工具,它将对象模型表示的数据映射到SQL表示的关系模型上去。Hibernate管理java到数据库的映射,提供给了数据查询和存取的方法,减少了程序员对数据持久化层相关的编程任务。
### 持久化与ORM
什么是持久化?是把数据保存到数据库或者某些存储设备中。在三层架构中,持久化是和数据库打交道的层次。在jsp的web开发中,经常有许多数据库连接、删除、查询等操作,在数据库相关工作中通过jdbc过于繁琐,就催生出了ORM(Object-RelationMapping),ORM作用是在关系数据库和对象之间做一个自动映射,这样在操作数据库时不需要使用复杂的sql语句,只要操作对象即可,ORM工具会自动将对象转换为sql语句操作。这样就只需要关注业务逻辑中的对象结构,而不用关心底层复杂的sql和jdbc代码。而Hibernate框架是ORM工具的一种,是关系/对象映射的解决方案(如图1.1)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087ecbadf.jpg)
图1.1
### Hibernate详细架构
Hibernate高层架构图和详细架构图如下图所示(图1.2和图1.3)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bade1c4.jpg)
图1.2高层架构图
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_576908bb07eb7.jpg)
图1.3详细架构图
一些介绍。
(1) 会话工厂(SessionFactory)
会话工厂是对属于单一数据库的映射文件的线程安全的、不可变的缓存快照。它是会话的工厂类,可能持有一个可选的(二级)数据库缓存,可以在进程级别或集群级别保存可以在事务中重用的数据。
(2) 会话(Session)
对于Session我们是那么的熟悉,在javaweb开发中session可以用来保存用户会话过程中状态信息。而hibernate中的session是对象与数据库会话持久化管理器。
会话是单线程、声明短暂的对象,代表应用和持久化层之间的一次对话,封装了JDBC连接,事务的工程。保存必需持久化对象缓存,用于遍历对象图,或者表示查找对象。
(3) 持久化对象(PersistentObjects)和集合(Collection)
持久化对象是生命周期短暂的单线程对象,包含了持久化状态,他们从属于且仅从属于一个session。
(4) 临时对象(TransientObjects)和集合(Collection)
没有从属于一个session的持久化类的实例。它们可能是刚被程序实例化,还没来得及持久化的对象,或者是一个已经被关闭的Session所是实例化。
(5) 事务(Transaction)
单线程,应用程序用它表示一批不可分割操作。一个session在某些情况下可能跨越多个事务。
(6) 连接提供者(ConnectionProvider)
JDBC连接工厂和池,从底层的Datasource或者DriverManager抽象而来。对应用程序不可见,但是可以被开发扩展或实现。
(7) 事务工厂(TransationFactory)
事务实例的工厂,对应用程序不可见,但可以被开发者扩展或实现。
### Hibernate优点和缺点
优点:Hibernate持久层与数据库打交道的桥梁,彻底封装了JDBC,隐藏了更多的细节,有很好的移植性。JDBC语句繁琐,赋值执行我们不用再写相关代码,提高了效率,同时hibernate是一个轻量级框架(不依赖于别的运行),没有侵入性,测试简单,提高了生产力。
缺点:封装的太彻底导致不灵活,是用数据特定的东西比较不容易。对大量数据库的更新有问题,当程序大量查询统计,或批量更新无法使用数据库特性机制,例如存储过程等。
下一篇[Hibernate示例](http://blog.csdn.net/lovesummerforever/article/details/19170795)。
Struts旅程(六)Struts页面转发控制ActionForward和ActionMapping
最后更新于:2022-04-01 14:49:43
上篇讲述了[struts控制器Action和DispatchAction](http://blog.csdn.net/lovesummerforever/article/details/18967831)以及LookupDispatchAction,本篇主要说说struts中的页面转发控制,struts提供了ActionForward和ActionMapping两个类来控制页面转发。
### ActionForward
在使用struts框架开发web应用程序时,Action在完成用户逻辑处理后,需要把处理结果展示给用户,这个时候就需要程序控制页面的转发,在struts中使用ActionForward对象控制程序转向。ActonForward对象是一种配置对象,代表了一般的web资源,可以是jsp页面,servlet以及其他的Action,ActonForward对象映射的是struts配置文件struts-config.xml中的<forward>元素,在这个元素中封装了目标页面的URI。ActionForward是struts中的核心类,以下主要参数。
(1)Name属性:指定ActionForward名称,在其他类中也可以通过这个名字调用ActionForward。
(2)Path属性:指明了ActionForward对应的URI。
(3)redirect属性:属性设置为true时被设置为重定向,默认false。
在struts中ActionForward有全局转发ActionForward和局部转发ActionForward,全局ActionForward应用于全部的Action和局部的转发仅仅对于当前的Action有效。
例如对用户登录的判断,提交表单后,把用户登录信息保存到session中,然后设置<forward>中redirect为true,设置为重定向方式,然后再进行判断
~~~
<action path="/must_login" type="com.bjpowernode.struts.MustLoginAction"
>
<forwardname="login" path="/login.jsp"redirect="true"/>
</action>
~~~
这样当用户已经登录的时候会给出提示已经登录。
如果有十个Action,每个都要验证是否登录,每个Action都要进行判断是否登录,如果没有登录或者超时登录则转到Login.jsp上。<forwardname=”login” path=”/login.jsp” redirect=”true”/>如果有100个Action都要重复一百个上一行代码,因为这个forward只能被自己的这个Action使用,不能被其他的Action使用,所以我们称为局部的Action。为了避免不必要的重复我们可以使用全局Action,代码如下所示。
~~~
<global-forwards>
<forwardname="login" path="/login.jsp"redirect="false"/>
</global-forwards>
~~~
虽然通过对session的判断和全局forward的设置,但是用户可以通过输入地址来访问jsp页面。我们可以把页面放到WEB-INF下,WEB-INF下的页面是无法在客户端访问到的,我们把页面放到这个目录下,避免用户通过输入地址访问到我们不想让用户访问的页面。
我们也可以自己手动转到想要转到的页面,不需要返回ActionForward对象。
~~~
//手动转向。
Response.sendRedirect(request.getContextPath()+”/login.jsp”);
//同时设置返回值为null。
Return null;
~~~
ActionForward和动态ActionForward。
我们想要实现当用户输入1转到页面page1.jsp,输入2转到page2.jsp页面。代码如下所示。
~~~
packagecom.bjpowernode.struts;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.struts.action.Action;
importorg.apache.struts.action.ActionForm;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
/**
*动态的ActionForward
* @author summer
*
*/
public classDynaActionForwardTestAction extends Action {
@Override
public ActionForwardexecute(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
intpage = Integer.parseInt(request.getParameter("page"));
//自己new一个ActonForward。动态ActionForward
ActionForwardaf = new ActionForward();
af.setPath("/page"+ page+".jsp");
//设置为重定向方式。
af.setRedirect(true);
//如果输入时1则重定向到page1.jsp.
if(page==1)
{
af= mapping.findForward("page1");
}
//如果输入时2则重定向到page2.jsp.
if(page==2)
{
af= mapping.findForward("page2");
}
returnaf;
}
}
~~~
在这里采用动态ActionForward实现设置为重定向方式,而静态的ActionForw是不能动态的修改配置文件的。
### ActionMapping
在struts-config.xml配置文件中,每个<action>元素都对应着一个ActionMapping,当用户请求被ServletAction接受以后,ActionServlet会根据用户请求URL以及<action>元素设定的path属性确定对应的ActionMapping对象,ActionMapping对象会告诉ActionServlet使用哪个Action对象处理用户请求。
ActionMapping描述了struts中用户请求路径和Action的映射关系,在struts中每个ActionMapping都是通过path属性和一个特定的用户请求URL关联。它负责转发用户请求给具体的Action,同时还转发了其他的一些相关信息,这种请求和处理动作之间的映射关系保存在struts-config.xml这个配置文件中,在web服务器初始化的时候,会加载这个配置文件,struts给每个Action都创建一个ActionMapping对象,用来提供给Action使用,当ActionServlet转发请求的时候,ActionMapping对象会被作为参数传递给Action的execute()方法。
struts的学习就总结到此,学习永远是在学习中,不是一蹴而就的,在后续的学习和项目中加深学习,加深认识。加油!
Struts旅程(五)struts控制器DispatchAction
最后更新于:2022-04-01 14:49:41
上篇演示了[struts](http://blog.csdn.net/lovesummerforever/article/details/18963959)[框架的由来](http://blog.csdn.net/lovesummerforever/article/details/18963959),从而体现struts框架优点。Struts中的表单处理器为ActionForm,而struts中的控制器主要是Action,以及DispatchAction控制器等。
**Action**
在struts中,所有的用户都会经过ActionServlet的处理,而实际的工作是交给Action对象来处理的,ActionServlet可以从配置文件中创建ActionMapping对象,从ActionMapping对象中找到对应使用的Action,然后将用户请求转交给Action。
对Struts一个ActionMapping只能生成一个Action对象,当用户发起请求的时候会检查所需要的Action对象是否存在,如果不存在就会生成一个Action对象,在以后的处理过程中使用这个对象。
当我们使用Action的时候需要继承arg.apache.struts.action.Action这个类,在子类中加入所需要的业务逻辑处理,这些子类会返回ActionForward对象,ActionServlet接受这个对象,把页面转发到指定页面,从而把用户请求的结果发送到对应的页面。我们在struts-config.xml中进行配置。配置的主要属性如下:
(1) path属性:访问Action的URL地址,当用户请求路径和URL匹配时,ActionServlet会把这个用户请求发送给Action处理。
(2) type属性:指定处理请求的Action对应的类,需要写类文件的包路径。
(3) name属性:指定我们的Action用到的ActionForm名字,这个ActionForm必须是在<form-beans>中定义过的。
(4) scope属性:指定ActionForm的使用范围,缺省值为session范围。
(5) input属性:指定表单验证出错的时候转向页面。
(6) validate属性:指明是否自动调用ActionForm中的validate方法对表单进行验证。
配置示例如下代码。
~~~
<struts-config>
<form-beans>
<form-bean name="loginForm" type="com.bjpowernode.struts.LoginActionForm" />
</form-beans>
<action-mappings>
<action path="/login"
type="com.bjpowernode.struts.LoginAction"
name="loginForm"
scope="request"
>
<forward name="success" path="/login_success.jsp"/>
<forward name="error" path="/login_error.jsp"/>
</action>
</action-mappings>
</struts-config>
~~~
**问题**
当我们完成用户增删改查操作时采用struts框架,我们需要为增删改查建立四个不同的Action,如果有更多的增删改查操作,比如对物料增删改查也需要建立四个Action,这样造成了大量的Action。
**问题的解决**
在struts中的Action类中,只提供了一个execute()方法,一个用户请求URL只能对应一个servlet,在struts中提供了另一个控制器类org.apache.struts.actions.DispatchAction,这个类可以经完成相关业务逻辑所需方法几种在一个DispatchAction类中,我们继承DispatchAction类后不重写execute()方法,而是编写自己的方法,在不同的方法中处理不同的动作。删除用户增删改查对应的Action,建立UserAction(如图5.1)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087eafd49.jpg)
图5.1
界面中调用代码如下所示。
~~~
<body>
<a href="user/user_maint.do?command=list"title="请点击访问用户管理系统">用户管理系统</a>
</body>
~~~
其中list对应着UserAction中的list方法,传递的字符串与UserAction中的方法名相同。
UserAction中的代码如下所示:
~~~
packagecom.bjpowernode.drp.web.actions;
importjava.util.Date;
importjava.util.List;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.commons.beanutils.BeanUtils;
importorg.apache.struts.action.ActionForm;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
importorg.apache.struts.actions.DispatchAction;
importcom.bjpowernode.drp.manager.UserManager;
importcom.bjpowernode.drp.model.User;
importcom.bjpowernode.drp.web.forms.UserActionForm;
public classUserAction extends DispatchAction {
protected ActionForward list(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
//调用业务逻辑操作
List userList = UserManager.getInstance().findAllUserList();
request.setAttribute("userlist",userList);
returnmapping.findForward("list_success");
}
/**
* 用户删除
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward del(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throws Exception {
//获取从页面表单中提交过来的值
UserActionForm uaf = (UserActionForm)form;
//取得需要删除的userId的集合
String[] userIdList = uaf.getSelectFlag();
//调用业务逻辑操作
UserManager.getInstance().deleteUsers(userIdList);
return mapping.findForward("del_success");
}
/**
* 用户添加
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward add(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throwsException {
//获取从页面表单中提交过来的值
UserActionForm uaf = (UserActionForm)form;
Useruser = new User();
BeanUtils.copyProperties(user,uaf);
user.setCreateDate(newDate());
//调用业务逻辑操作
UserManager.getInstance().addUser(user);
returnmapping.findForward("add_success"); }
/**
* 修改用户
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward modify(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
//获取从页面表单中提交过来的值
UserActionForm uaf = (UserActionForm)form;
User user = new User();
BeanUtils.copyProperties(user,uaf);
//调用业务逻辑操作
UserManager.getInstance().modifyUser(user);
returnmapping.findForward("modify_success");
}
/**
* 根据ID查询用户
*
* @param mapping
* @param form
* @param request
* @param response
* @return
* @throws Exception
*/
public ActionForward find(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
//获取从页面表单中提交过来的值
UserActionForm uaf = (UserActionForm)form;
String userId = uaf.getUserId();
//调用业务逻辑操作
User user = UserManager.getInstance().findUserById(userId);
//将user对象从Action传递到JSP页面
request.setAttribute("user",user);
returnmapping.findForward("find_success");
}
}
~~~
Struts-config.xml配置文件代码如下所示。
~~~
<struts-config>
<form-beans>
<form-bean name="userForm"type="com.bjpowernode.drp.web.forms.UserActionForm"/>
</form-beans>
<action-mappings>
<action path="/user/user_maint"
type="com.bjpowernode.drp.web.actions.UserAction"
name="userForm"
scope="request"
parameter="command"
>
<forward name="list_success" path="/user/user_list.jsp"/>
<forward name="del_success" path="/user/user_maint.do?command=list"redirect="true"/>
<forward name="add_success" path="/user/user_maint.do?command=list"redirect="true"/>
<forward name="modify_success" path="/user/user_maint.do?command=list"redirect="true"/>
<forward name="find_success" path="/user/user_modify.jsp"/>
</action-mappings>
</struts-config>
~~~
其中配置Action的时候,配置了parameter属性,并且指定了parameter属性值为command,当用户单击添加或删除用户操作时,会以[http://localhost:8080/struts_dispatchaction_usermgr/user/user_maint.do?command=list](http://localhost:8080/struts_dispatchaction_usermgr/user/user_maint.do?command=list),这个请求会被映射到UserAction控制器中,Struts根据method参数的值把这个请求发送到控制器UserAction的list方法。这样取得参数完成页面的调用。
从上述可以看出,DispatchAction可以通过command这个参数的值来决定调用DispatchAction的哪个方法,DispatchAction是从用户请求的URL中提取parameter定义参数的值,从而决定调用哪个方法处理用户请求。所以DispatchAction不能通过ActionForm向服务器提交请求,因为提交表单的时候不能向服务器传递参数。
根据上述示例我们可以总结出DispatchAction与Action区别:Action是从表单中取得数据,并且自动转换为对应的类型。而DispatchAction取得配置文件中parameter,截取parameter定义的参数值。但是DispatchAction可以处理多个动作而不需要建立多个Action。
DispatchAction可以在同一个控制器中处理多个动作,但只能通过URL来调用控制器,根据用户提交的参数来决定调用哪个方法来处理用户请求。不能通过表单提交用户请求信息,在struts中如果要在同一个表单中处理不同的动作,可以使用LookupDispatchAction。在这里就不详细讲述了,有兴许的童鞋可以查找些资料来实现。
下一篇struts页面转发控制ActionForward和ActionMapping。
·
Struts旅程(四)MVC向struts MVC框架演变过程
最后更新于:2022-04-01 14:49:39
上一篇提出了问题,使用Struts框架比不使用struts框架的好处在哪里的问题。以及由此讲述了[静态](http://blog.csdn.net/lovesummerforever/article/details/18951649)[ActionForm和动态ActionForm](http://blog.csdn.net/lovesummerforever/article/details/18951649)。本篇就第一个问题,以一个示例对用户信息的增删改查来展示基本的MVC框架到strutsMVC的过程。
### 版本一 基本的MVC
首先是创建一个jsp索引页面index.jsp,index.jsp创建一个表单。
如果我们完成添加用户的模块,我们提交表单的时候要把这个表单提交给一个servlet,于是我们在src自己的包中建立自己的servlet继承HttpServlet,TestServlet同时覆盖父类的doGet和doPost方法。
提交的的表单信息,我们可以在TestServlet中通过request.getParameter(“username”);取出来。之后把从表单中的数据提交到数据库,我们的servlet作为控制器没有添加到数据库的职责,于是我们把访问数据库的业务逻辑放到UserManager.java类中。在这个类中实现添加用户的任务。
~~~
package com.bjpowernode.servlet;
public class UserManager {
public void add(String username)
{
System.out.println("UserManager.add()-->>"+ username);
}
}
~~~
在TestServlet中调用UserManager中的方法。同时让页面转向到添加成功的页面add_success.jsp。
TestServlet代码以及在web.xml中配置TestServlet.
~~~
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.bjpowernode.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/servlet/TestServlet</url-pattern>
</servlet-mapping>
~~~
Testservlet代码如下所示:
~~~
package com.bjpowernode.servlet;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class TestServlet extendsHttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter(“username”);
UserManager userManager = new UserManager();
userManager.add(username);
request.getRequestDispatcher(“/add_success.jsp”).forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request,response);
}
}
~~~
索引页面index.jsp代码,通过servlet/TestServlet来转到TestServlet。
~~~
<form action="servlet/queryUser.action" method="post">
姓名:<input type="text" name="username"><br>
<input type="submit" value="提交"><br>
</form>
~~~
这样采用MVC完成了用户的添加任务。
### 版本二 通过判断标识变量
当我们不仅要完成用户的添加而是要完成用户的增删改查的时候我们可以在servlet中进行判断,是添加还是删除等。并且在传递字符串的时候要加上一个标识表示相应的操作。这样在servlet中会有很多的if和else语句。
### 版本三 servlet模式匹配 截取字符串判断
我们可以在配置servlet的时候配置成为匹配模式<url-pattern>*.action</url-pattern>任何的*.action的都会转到到TestServlet,这样我们可以截取URL来判断转到哪个页面。但在TestServlet中仍然有很多的ifelse语句进行判断。这样我们的TestServlet代码如下所示。
~~~
import java.io.IOException;
import java.util.List;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class TestServlet extendsHttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//截取url.
String requestURI = request.getRequestURI();
//System.out.println("requestURI="+ requestURI);
//截取http://localhost:8080/test_servlet/servlet/addUser.action test_servlet后面的东西。
String path = requestURI.substring(requestURI.indexOf("/", 1),requestURI.indexOf("."));
//截取得到虚目录。/servlet/addUser
System.out.println("path="+ path);
String username = request.getParameter("username");
UserManager userManager = new UserManager();
String forward = "";
//判断截取的path和哪个要加载的页面相等.
if("/servlet/delUser".equals(path))
{
userManager.del(username);
forward = "del_success.jsp";
//request.getRequestDispatcher("/del_success.jsp").forward(request,response);
}else if("/servlet/addUser".equals(path))
{
userManager.add(username);
forward= "add_success.jsp";
//request.getRequestDispatcher("/add_success.jsp").forward(request,response);
}else if("/servlet/modifyUser".equals(path))
{
userManager.modify(username);
forward= "modify_success.jsp";
//request.getRequestDispatcher("/modify_success.jsp").forward(request,response);
}else if("/servlet/queryUser".equals(path))
{
List userList = userManager.query(username);
request.setAttribute("userList",userList);
forward= "query_success.jsp";
//request.getRequestDispatcher("/query_success.jsp").forward(request,response);
}else
{
throw new RuntimeException("请求失败!");
}
request.getRequestDispatcher(forward).forward(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throwsServletException, IOException {
doGet(request,response);
}
}
~~~
UserManager代码如下所示:
~~~
package com.bjpowernode.servlet;
import java.util.ArrayList;
import java.util.List;
public class UserManager {
public void add(String username)
{
System.out.println("UserManager.add()-->>"+ username);
}
public void del(String username)
{
System.out.println("UserManager.del()-->>"+ username);
}
public void modify(String username)
{
System.out.println("UserManager.modify()-->>"+ username);
}
public List query(String username)
{
System.out.println("UserManager.query()-->>"+ username);
List userList = new ArrayList();
userList.add("a");
userList.add("b");
userList.add("c");
return userList;
}
}
~~~
同时建立查询成功,删除成功,修改成功jsp页面。
这样在index.jsp页面中通过表单的action属性来转到相应的页面。
~~~
<body>
<form action="servlet/queryUser.action" method="post">
姓名:<input type="text" name="username"><br>
<input type="submit" value="提交"><br>
</form>
</body>
~~~
版本三的缺点是if太多,不稳定,当我们添加或者删除一个if的时候还需要重新编译源程序。这样无法适应变化的需求。所以我们在此基础上进行改进就需要去掉if语句,可以把改变的部分分离出来,变成可配置的,这样就变得灵活的,需要改动jsp的文件名,在配置文件中配置一下就可以了。
### 版本四 if else抽象出接口和类+配置文件
首先我们把转到的地址字符串放到一个字符串变量中,这样TestServlet中的doGet方法,代码如下。
~~~
String username = request.getParameter("username");
UserManager userManager = new UserManager();
String forward = "";
//判断截取的path和哪个要加载的页面相等.
if("/servlet/delUser".equals(path))
{
userManager.del(username);
forward= "del_success.jsp";
}else if("/servlet/addUser".equals(path))
{
userManager.add(username);
forward= "add_success.jsp";
}elseif("/servlet/modifyUser".equals(path))
{
userManager.modify(username);
forward= "modify_success.jsp";
}else if("/servlet/queryUser".equals(path))
{
List userList = userManager.query(username);
request.setAttribute("userList",userList);
forward= "query_success.jsp";
}else
{
thrownew RuntimeException("请求失败!");
}
request.getRequestDispatcher(forward).forward(request,response);
~~~
统一完成了转向。当if语句是满足某种条件,条件不同的时候转到不同的页面,添加有添加的逻辑,修改有修改的逻辑,这样我们可以抽象出接口。对添加的action操作添加,对删除的action做出删除任务,对修改的action做出修改。把每一个if语句中的变成为类去实现,抽象出一个Action接口,接口中方法execute()方法,返回转向路径字符串。类图如下所示(图4.1)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087e70429.jpg)
图4.1
共同的Action接口,不同的实现。
Action接口代码。
~~~
package com.bjpowernode.servlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public interface Action {
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
~~~
AddUserAction代码。
~~~
package com.bjpowernode.servlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class AddUserActionimplements Action {
@Override
public String execute(HttpServletRequest request,HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
//Stringsex = request.getParameter("sex");
//...........
//调用业务逻辑.
UserManager userManager = new UserManager();
userManager.add(username);
return"/add_success.jsp";
}
}
~~~
DelUserAction代码。
~~~
package com.bjpowernode.servlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class DelUserActionimplements Action {
@Override
public String execute(HttpServletRequest request,HttpServletResponse response) throws Exception {
String username = request.getParameter("username");
//String sex = request.getParameter("sex");
//...........
//调用业务逻辑.
UserManager userManager = new UserManager();
try
{
userManager.del(username);
}catch(Exceptione)
{
return"del_error.jsp";
}
return"/del_success.jsp";
}
}
~~~
ModifyUserAction代码。
~~~
package com.bjpowernode.servlet;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class ModifyUserActionimplements Action {
@Override
public String execute(HttpServletRequest request,HttpServletResponse response) throwsException {
String username = request.getParameter("username");
//String sex = request.getParameter("userId");
//...........等其他...
//调用业务逻辑.
UserManager userManager = new UserManager();
userManager.modify(username);
return"/modify_success.jsp";
}
}
~~~
QueryUserAction代码。
~~~
package com.bjpowernode.servlet;
import java.util.List;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
public class QueryUserActionimplements Action {
@Override
public String execute(HttpServletRequest request,HttpServletResponse response) throwsException {
String username = request.getParameter("username");
//Stringsex = request.getParameter("userId");
//其他查询条件.
//...........等其他...
//调用业务逻辑.
UserManager userManager = new UserManager();
List userList = userManager.query(username);
request.setAttribute("userList",userList);
return"/query_success.jsp";//转向路径都可以通过配置文件读取。
}
}
~~~
这样使用多态方式调用不同的Action,转向代码如下所示。
~~~
//用多态的方式.
Action action = null;
if("/servlet/delUser".equals(path))
{
action= new DelUserAction();
}else if("/servlet/addUser".equals(path))
{
action= new AddUserAction();
}else if("/servlet/modifyUser".equals(path))
{
action= new ModifyUserAction();
}else if("/servlet/queryUser".equals(path))
{
action= new QueryUserAction();
}else
{
throw new RuntimeException("请求失败!");
}
//取得action后传递过去。动态调用ACtion中的execute方法。
String forward = null;
try{
forward= action.execute(request,response);
}catch (Exception e) {
e.printStackTrace();
}
//根据路径完成转向。
request.getRequestDispatcher(forward).forward(request, response);
~~~
上述调用不同的action,我们可以把不同的请求和对应的action配置到自己的xml文件中。配置哪个请求对应哪个Action。
~~~
<action-config>
<action path="/servlet/delUser" type="com.bjpowernode.servlet.DelUserAction">
<forward name="success">/del_success.jsp</forward>
<forward name="error">/del_error.jsp</forward>
</action>
<action path="/servlet/addUser" type="com.bjpowernode.servlet.AddUserAction">
<forward name="success">/add_success.jsp</forward>
<forward name="error">/add_error.jsp</forward>
</action>
<action path="/servlet/modifyUser" type="com.bjpowernode.servlet.ModifyUserAction">
<forward name="success">/modify_success.jsp</forward>
<forward name="error">/modify_error.jsp</forward>
</action>
<action path="/servlet/queryUser" type="com.bjpowernode.servlet.QueryUserAction">
<forward name="success">/query_success.jsp</forward>
<forward name="error">/query_error.jsp</forward>
</action>
</action-config>
~~~
这样我们就可以读取配置文件来进行相应的操作。每个action对应着一些信息,它的class,执行成功以及执行失败的配置。我们在读取配置文件的时候可以把这些放到Map对象中。代码如下所示。
~~~
ActionMapping
{
private String path;
private String type;
Map forwardMap;
}
forwardMap
{
key="success";
value="/del_success.jsp"
key="error";
value="/del_error.jsp";
}
Map map = new HashMap();
map.put("/servlet/delUser",);
map.put("/servlet/addUser",);
map.put("/servlet/modifyUser",);
map.put("/servlet/queryUser",);
//如果是删除的ActionMapping存储如下:
actionMapping
{
path="/servlet/delUser";
type="com.bjpowernode.servlet.DelUserAction";
forwardMap
{
key="success",value="/del_success.jsp";
key="error",value="/del_error.jsp";
}
}
String path ="/servlet/delUser";
//根据截取的URL请求,到Map中取得本次请求对应的Action。
ActionMapping actionMapping =(ActionMappint)map.get(path);
// 取得本次请求对应的Action类的完成路径。
String type =actionMappint.getType();//com.bjpowernode.servlet.DelUserAction
//通过反射动态实例化Action
Aciton action =(Action)class.forName(type).newInstance();
//取得action后传递过去。动态调用ACtion中的execute方法。
String forward =action.execute(request,response);
//根据路径完成转向。
request.getRequestDispatcher(forward).forward(request,response);
~~~
我们用时序图来描述上述调用过程(如图4.2)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087e8e6e9.jpg)
图4.2
Struts就是采用上述思路。Struts框架是把上图中前端控制器servlet进行了封装,我们只要继承struts的Action去调用业务逻辑和转向。读取配置文件的工作也是封装到了struts框架中。前篇讲解了struts框架的应用实例,我们可以进行对比。
### 版本五 采用struts框架
[采用struts框架完成登录示例](http://blog.csdn.net/lovesummerforever/article/details/17348871)。
这样我们对struts由来,以及struts封装了哪些东西从而方便了程序员,不再重复的写重复的代码。下一篇[struts控制器DispatchAction.](http://blog.csdn.net/lovesummerforever/article/details/18967831)
Struts旅程(三)Struts表单处理器ActionForm(静态动态)
最后更新于:2022-04-01 14:49:36
上篇我们以[登录为例](http://blog.csdn.net/lovesummerforever/article/details/17348871)讲解了struts配置,实现了使用struts框架进行登录的示例。一些名词已经在脑海中飘荡了。
**ActionServlet**:struts的控制器,负责截取URL或分发。提供Model(模型层)和View(视图层)使用,因此可将它看作是模型和视图之间的中介。
**ActionForm**:用于封装用户的请求参数,而请求参数是通过JSP页面的表单域传递过来的。
**Action**:用户请求和和业务逻辑的桥梁,每个Action作为业务逻辑的代理,可以调用业务逻辑。
一些问题是有必要再次提起的。
**使用基本的MVC和使用struts框架区别,使用struts好处在哪里?**
我们知道我们不适用框架的时候,在MVC中典型的控制器是servlet,servlet可以获取参数和逻辑模型的调用和转向功能。而struts对它做了封装,为什么要进行封装?当我们请求到一个servlet,我们在这个servlet中取得参数、调用业务逻辑、转向,我们在servlet中写死了转向页面,当我们想要换一个转向页面的时候就需要改动代码了,改动代码后还要重新进行编译。
而且从表单中传递过来的数据全是字符串形式,我们还需要根据实际的需求把字符串转换为我们需要的类型,如果很多地方需要转换,并且每次使用每次都要进行转换,有没有一种机制,把表单中的字符串拿过来自动转换为相应的类型呢?不需要我们再进行手动转换呢?
基于上述的不便,转向不灵活,表单中的字符串每次都要进行转换等一系列的原因,struts把这些做了封装。提取出重复的操作,转向信息放到了配置文件中,这样更加灵活了。
在上述问题中,阐述了struts对表单的封装,在web应用程序开发过程中,开发人员需要大量的时间来处理表单问题,有的时候是通过表单提交一些新的问题,有的是通过表单修改数据,所有这些表单在处的处理在传统web开发中是非常复杂的。本篇重点说说struts中的表单处理器ActionForm。
**ActionForm**
**问题的提出**
在传统的web应用程序开发中,繁杂的表单处理给开发工作人员带来了巨大的困难,在传统的开发语言中,没有组建可以自动收集用户输入的表单内容,开发人员不得不在程序中手动提取表单的值。例如在表单中有这样的一个文本输入域:<inputtype=”text” name=”password”> 要在程序中取得这个文本输入域的值,只能用这样的方法:request.getParameter(“password”);这样的处理方法在表单比较小的时候是可以使用的,但是当表单输入项较多的时候就不得不大量重复类似上面的处理。
**问题的解决**
在Struts中就是使用ActionForm来解决这个问题,对于每一个用户的表单,需要提供一个ActionForm,这个ActionForm自动把客户提交的表单保存在这个ActionForm中,然后把这个ActionForm传递给Action,从而在Action中可以通过这个ActionForm取出用户信息,然后根据这些信息完成对应的业务逻辑处理。
例如在Struts中用struts的html标签表述成下述形式:
<html:text property=”password”/>
在这种情况下表单提交后,struts框架会自动把表单中的这个输入项赋值到ActionForm中的password属性中,从而把表单中的内容保存在ActionForm中,整个过程由struts自动完成,不需要开发人员干涉。我们在创建ActionForm时要遵循以下规范:
(1)每个ActionForm都要继承org.apache.struts.action.ActionForm类,而且需要为每一个表单提供一个ActionForm。
(2)ActionForm中每个属性要与表单中的输入项一一对应。
(3)AcitonForm每个属性都要提供的getter方法和setter方法。Struts框架就是通过这些方法来保存表单的值,然后在Action中通过这些方法取得表单的值。
(4)如果表单需要验证,就需要在ActionForm中提供validate方法,这个方法中提供对表单的具体验证逻辑。这个方法不仅实现了数据验证同时实现了数据缓冲的作用,在validate方法中验证用户提交表单的有效性,当表单验证失败时会自动返回用户输入页面,这时候用户输入的值都保存在ActionForm中,返回页面时struts框架会取出AcitonForm中的数据并输出到对应的用户输入项中,保证了用户开始输入的表单信息。
**问题的提出**
以上所说的是静态ActionForm,当我们为每个表单都创建一个AcitonForm的时候,会导致ActionForm数量过多。每个ActionForm过强的聚合性也会使代码难以维护和重用。怎么样不用去创建过多的ActionForm?而且当提交表单的属性名字相同时,不用再重复创建AcitonForm(例如登录和注册)?
**问题的解决**
Struts中可以使用动态ActionForm来解决上述问题。动态ActionForm不需要创建自己的ActionForm,需要在创建自己Action的时候直接把execute方法中传递过来的form对象转化为DynaActionForm。
我们需要更改struts-config.xml中form-beans配置:
~~~
<form-beans>
<form-bean name="dynaForm" type="org.apache.struts.action.DynaActionForm">
<form-property name="username" type="java.lang.String" />
<form-property name="age" type="java.lang.Integer"/>
</form-bean>
</form-beans>
~~~
Action中使用get方法取得表单中的值。
~~~
/**
* 测试动态ActionForm.
* @author summer
*
*/
public classDynaActionFormTestAction extends Action {
@Override
publicActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequestrequest, HttpServletResponse response)
throwsException {
DynaActionFormdaf = (DynaActionForm)form;
//取出map中key值为name,value为类名.
Stringusername = (String)daf.get("username");
Integerage = (Integer)daf.get("age");
System.out.println("username"+username);
System.out.println("username"+age);
returnmapping.findForward("success");
}
}
~~~
静态ActionForm方式,使用get/set方法,而动态ActionForm方式,使用map的getkey方式,其中key就是标签name的值。
使用动态ActionForm优点:若更改表单和ActionForm时不需要重新编译,而静态的需要更改静态的ActionForm.java文件,必须重新编译。缺点:静态返回的是对应的值,动态ActionForm返回的是对象,我们还要把这个对象进行强制转换。
下一篇[MVC向struts MVC框架演变过程](http://blog.csdn.net/lovesummerforever/article/details/18963959)
struts旅程(二)Struts登录示例
最后更新于:2022-04-01 14:49:34
上一篇我们简单了解了[struts](http://blog.csdn.net/lovesummerforever/article/details/18942381)[原理](http://blog.csdn.net/lovesummerforever/article/details/18942381),学习SSH,第一部是傻瓜式学习法,人家怎么做就跟着怎么做就ok。我们以登录为例,Struts配置步骤总结如下(如图2.1):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087e2ace6.jpg)
图2.2
### 1、jar包拷贝
首先是建立java web项目,之后打开我们我们下载好strtus框架,Struts-1.2.9-bin文件夹和struts-1.2.9.src源文件文件夹。在bin文件夹中的lib文件中拷贝struts的jar包,拷贝到我们自己项目struts_login –>lib文件夹下(如图2.2)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-06-21_5769087e4b5fa.jpg)
图2.2
### 2、web.xml文件配置
找到Struts-1.2.9-bin中Struts-1.2.9-bin-->webapps下的struts实例struts-blank中的struts-1.2.9-bin\webapps\struts-blank\WEB-INF下的web.xml文件,复制配置对ActionServlet的配置,粘贴到我们的项目struts_login的WEB-INF下的web.xml中,代码如下所示。主要是对struts自带的ActionServlet进行配置。
~~~
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>2</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<!--Standard Action Servlet Mapping -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
~~~
### 3、在项目中建立自己的ActionForm
在项目中建立自己的ActionForm,继承struts框架中已经写好的ActionForm,在ActionForm设置用到的数据,并且要和我们界面上设置的名称一致。因为我们在提交表单的时候,所有的请求都会放到ActionForm中。建立登录的ActionForm,LoginActionForm.java代码如下所示。
~~~
package com.bjpowernode.struts;
import org.apache.struts.action.ActionForm;
/**
* 登录ActionForm,负责表单收集数据.
* 表单的属性必须和ActionForm中的get和set的属性一致.
* @author summer
*
*/
public classLoginActionForm extends ActionForm {
//用户名。
private Stringusername;
//密码。
private String password;
//设置密码。
public voidsetPassword(Stringpassword) {
this.password = password;
}
//得到用户名。
public StringgetUsername() {
return username;
}
//设置用户名。
public voidsetUsername(Stringusername) {
this.username = username;
}
//得到密码。
public StringgetPassword() {
return password;
}
}
~~~
### 4、建立自己的Action
建立自己的Action,同时继承struts框架中的org.apache.struts.action.Action,重载父类的execute方法。在这里完成取出表单中的数据。通过CalActionFormcalForm= (CalActionForm)(CalActionForm)form;(struts框架中已经帮我们封装好了,我们去使用就可以了)来取得表单中的值。经过判断后,进行相应的操作,跳转到相应的页面。Action的功能是负责拿到表单数据和调用业务逻辑后进行页面跳转。建立登陆的Action类,LoginAction.java类,调用业务逻辑类UserManager的login方法。代码如下所示。
~~~
packagecom.bjpowernode.struts;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importorg.apache.struts.action.Action;
importorg.apache.struts.action.ActionForm;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
/**
*登录Action
*负责取得表单数据,调用业务逻辑,返回转向信息.
*
* @author summer
*
*/
public classLoginAction extendsAction {
@Override
public ActionForward execute(ActionMappingmapping,ActionForm form,
HttpServletRequest request, HttpServletResponseresponse)
throws Exception {
LoginActionForm laf = (LoginActionForm)form;
Stringusername = laf.getUsername();
Stringpassword = laf.getPassword();
UserManager userManager = newUserManager();
//传递用户名和密码
try
{
userManager.login(username, password);
request.setAttribute("username", username);
return mapping.findForward("success");
}catch(UserNotFoundException e)
{
e.printStackTrace();
request.setAttribute("msg","用户不能找到,用户名称=[" +username +"+]");
}catch(PasswordErrorException e)
{
e.printStackTrace();
request.setAttribute("msg","密码错误");
}
return mapping.findForward("error");
}
}
~~~
### 5、建立struts-config.xml
作为Struts框架的核心描述,struts-config.xml可以说“一切尽在掌握”。它不但描述了MVC模型,定义所有视图层和控制层之间的接口(ActionForm),与控制层和模型层的接口(Action)进行结合,而且可以定义一些附加组件,如国际化信息资源排至文件,标签库信息等。
仍然是站在巨人的肩膀上,将我们下载的struts bin文件夹中的struts-config.xml文件复制到我们的项目的WEB-INF中,删除struts-config.xml中的注释部分。把Action和ActionForm配置起来。ActionForm放到<form-beans></form-beans>中,Action配置放到<action-mappings></action-mappings>中,struts-config.xml配置代码如下所示。
~~~
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration1.2//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
<struts-config>
<form-beans>
<form-bean name="loginForm" type="com.bjpowernode.struts.LoginActionForm"/>
</form-beans>
<action-mappings>
<action path="/login"
type="com.bjpowernode.struts.LoginAction"
name="loginForm"
scope="request"
>
<forward name="success" path="/login_success.jsp"/>
<forward name="error" path="/login_error.jsp"/>
</action>
</action-mappings>
</struts-config>
~~~
其中form-beans元素中可以定义0个或1个以上的form-bean元素,每个form-bean被认为是一个ActionForm对象,name属性表示form-bean元素的名称,type属性指定其类名和路径。
Action-mappings元素用来包含零到多个action,其子元素action负责具体映射的详细信息。在action-mapping元素中可以定义0个或1个以上的action元素。每个action元素接受path属性定义的请求,并映射到type属性所定义的具体action对象。在映射过程中,将name属性定义的actionform一并传过去,它有如下属性:
Parameter,scope两个属性指定了传送方式和范围,scope常用的值有两个“session”和“request”。
Validate属性指定了是否需要actionform的验证。
Forward元素,将请求success转发到”**/**login_success.jsp**”**页面。
****
### 6、业务逻辑类UserManager和自定义异常类
代码如下所示:
~~~
packagecom.bjpowernode.struts;
publicclassUserManager {
publicvoid login(Stringusername,Stringpassword)
{
if(!"admin".equals(username))
{
thrownewUserNotFoundException();
}
if(!"admin".equals(password))
{
thrownewPasswordErrorException();
}
}
}
~~~
自定义异常类UserNotFoundException和PasswordErrorException代码如下所示。
~~~
packagecom.bjpowernode.struts;
public class UserNotFoundExceptionextends RuntimeException {
public UserNotFoundException() {
}
public UserNotFoundException(Stringmessage) {
super(message);
}
public UserNotFoundException(Throwable cause) {
super(cause);
}
public UserNotFoundException(Stringmessage,Throwable cause) {
super(message, cause);
}
}
packagecom.bjpowernode.struts;
public class PasswordErrorExceptionextends RuntimeException {
public PasswordErrorException() {
}
public PasswordErrorException(Stringmessage) {
super(message);
}
public PasswordErrorException(Throwable cause) {
super(cause);
}
public PasswordErrorException(Stringmessage,Throwable cause) {
super(message, cause);
}
}
~~~
### 7、视图jsp页面调用。
登录界面login.jsp,错误显示界面login_error.jsp,登录成功界面login_success.jsp。代码如下所示。
~~~
<%@pagelanguage="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPEhtml PUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Inserttitle here</title>
</head>
<body>
<form action="login.do" method="post">
用户:<inputtype="text" name="username"><Br>
密码:<inputtype="password" name="password"></br>
<input type="submit" value="登录">
</form>
</body>
</html>
~~~
Login_success.jsp.
~~~
<%@page language="java"contentType="text/html;charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Insert title here</title>
</head>
<body>
${username},登录成功!
</body>
</html>
~~~
Login_error.jsp界面。
~~~
<%@page language="java" contentType="text/html; charset=GB18030"
pageEncoding="GB18030"%>
<!DOCTYPE html PUBLIC "-//W3C//DTDHTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GB18030">
<title>Insert title here</title>
</head>
<body>
<%--
<%=request.getAttribute("msg") %>
--%>
${msg }
</body>
</html>
~~~
就这样我们实现了运用struts框架完成用户登录。就这样从初步学习到简单应用,随着应用的次数增多,我们会对struts理解越来越深刻,并且感受struts框架给我们带来的便捷。
下一篇[Struts表单处理器ActionForm(静态动态)](http://blog.csdn.net/lovesummerforever/article/details/18951649)