zoukankan      html  css  js  c++  java
  • day32_Hibernate学习笔记_04

    一、Hbernate中的日志框架_整合log4j(了解)

    • Hibernate 利用 Simple Logging Facade for Java (SLF4J)
      来记录不同系统事件的日志。SLF4J 可以根据你选择的绑定把日志输出到几个日志框架(NOP、Simple、log4j version
      1.2、JDK 1.4 logging、JCL 或 logback)上。
    • slf4j 核心jar:slf4j-api-1.6.1.jar 。slf4j 是日志框架,将其他优秀的日志第三方进行了整合。
    • 整合导入jar包,添加至构建路径
        1、log4j 核心包:log4j-1.2.17.jar
        2、过渡jar(整合jar):slf4j-log4j12-1.7.5.jar
    • 导入配置文件,该文件在 hibernate-distribution-3.6.10.Finalprojectetc 中,拷贝至项目的src 目录下
        log4j.properties,此配置文件配置log4j 如何输出日志。
    • 编写配置文件内容:
        1、记录器
        2、输出源
        3、布局
    • 记录器:
        例如:log4j.rootLogger=info, stdout, file
        格式:log4j.rootLogger=日志级别, 输出源1, 输出源2, ……
        log4j 日志级别:fatal 致命错误、error 错误、warn 警告、info 信息、debug 调试信息、trace 堆栈信息 (输出信息多少:日志信息量逐渐增加),项目上线时用error项目开发中用info
    • 输出源:
        例如:log4j.appender.file=org.apache.log4j.FileAppender
        格式:log4j.appender.输出源的名称=输出源的实现类
          输出源的名称:自定义
          输出源的实现类:log4j提供
        输出源的属性,例如:log4j.appender.file.File=d:mylog.log
        输出源的属性格式:log4j.appender.名称.属性=值
        每一个输出源对应一个实现类,实现类都是属性(setter),底层执行setter方法进行赋值。
    • 常见的输出源实现类:
        org.apache.log4j.FileAppender 输出文件中
          file,表示文件输出位置
        org.apache.log4j.ConsoleAppender 输出到控制台
          Target,表示使用哪种输出方式,在控制台打印内容,取值:System.out / System.err
    • 布局 => 确定输出格式
        例如:log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        格式:log4j.appender.数据源.layout=org.apache.log4j.PatternLayout
        布局属性:log4j.appender. 数据源.layout.ConversionPattern=值
          例如1:log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
          输出为:21:57:26,197 INFO SessionFactoryImpl:927 - closing
          例如2:log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
          输出为:closing
    • 扩展:对指定的目录设置日志级别
        例如:log4j.logger.org.hibernate.transaction=debug
        格式:log4j.logger.包结构=级别

    二、Hibernate的关联关系映射(一对一)(了解)

    • 情况1:主表的主键,与从表的外键(唯一),形成主外键关系。
    • 情况2:主表的主键,与从表的主键,形成主外键关系 (从表的主键又是外键,即主键同步)-- 推荐使用该方式
    • 如下图所示:

    2.1、情况1示例

    Company.hbm.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


    <hibernate-mapping  package="com.itheima.domain">
        <class name="Company" table="t_company">
            <id name="cid" column="cid">
                <generator class="native"></generator>
            </id>
            <property name="cname" column="cname" type="string"></property>

            <!-- 一方的配置:一对一,特殊的多对一。
                 one-to-one:默认使用主键同步策略完成一对一的表关系体现。
                    但是我们现在使用的是主外键引用来完成一对一的表关系体现。那么需要进行修正。
                    使用 property-ref="company"   值是对方引用本方的属性名称
            -->

            <one-to-one name="address" class="Address" property-ref="company"></one-to-one>
        </class>
    </hibernate-mapping>

    Address.hbm.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


    <hibernate-mapping  package="com.itheima.domain">
        <class name="Address" table="t_address">
            <id name="aid" column="aid">
                <generator class="native"></generator>
            </id>
            <property name="aname" column="aname" type="string"></property>

            <!-- 多方的配置:一对一,特殊的多对一。外键唯一(外键不能重复),提供外键名称,unique默认值是false -->
            <many-to-one name="company" class="Company" column="company_id" unique="true"></many-to-one>
        </class>
    </hibernate-mapping>

    测试代码如下:

    // 演示:一对一
    public class Demo1 {
        @Test
        // 一对一保存
        public void fun1() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            Company c = new Company();
            c.setCname("传智播客");

            Address a = new Address();
            a.setAname("北京市朝阳区平房乡红门村28号");

            // 注意:在一对一使用外键引用的时候,即情况1,外键所在的对象才能维护外键关系,另一方无法维护外键关系。
            a.setCompany(c); // 维护外键关系

            // 注意:在一对一使用主外键同步的时候,即情况2,双方都能维护外键关系。
            // a.setCompany(c); // 维护外键关系
            // c.setAddress(a); // 维护外键关系

            session.save(c);
            session.save(a);

            session.getTransaction().commit();
            session.close();
        }

        @Test
        // 一对一查询
        // 注意:Hibernate中一对一查询,一定会使用表连接查询,所以也就没有懒加载的问题了。
        public void fun2() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            Company c = (Company) session.get(Company.class, 1);
            System.out.println(c);

            session.getTransaction().commit();
            session.close();
        }
    }

    2.2、情况2示例

    Company.hbm.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


    <hibernate-mapping  package="com.itheima.domain">
        <class name="Company" table="t_company">
            <id name="cid" column="cid">
                <generator class="native"></generator>
            </id>
            <property name="cname" column="cname" type="string"></property>

            <!-- 一方的配置:一对一,真正的一对一。不需要进行修正了。
                 one-to-one:默认使用主键同步策略完成一对一的表关系体现。我们现在就使用默认的策略。
                  注意:此时可以使用以下3个属性。
                    cascade     lazy        fetch
            -->

            <one-to-one name="address" class="Address" ></one-to-one>
        </class>
    </hibernate-mapping>

    Address.hbm.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


    <hibernate-mapping  package="com.itheima.domain">
        <class name="Address" table="t_address">
            <id name="aid" column="aid">
                <!-- foreign:声明该键既是主键又是外键 -->
                <generator class="foreign">
                    <!-- 当本方作为外键时,注入的是引用对方的属性名称 -->
                    <param name="property">company</param>
                </generator>
            </id>
            <property name="aname" column="aname" type="string"></property>

            <!-- 一方的配置:一对一,真正的一对一。既是主键又是外键,需要在某一方声明外键 ,constrained表示约束,默认值是false-->
            <one-to-one name="company" class="Company" constrained="true"></one-to-one>
        </class>
    </hibernate-mapping>

    测试代码同上:

    三、二级缓存【掌握】

    3.1、介绍

    3.1.1、缓存

      缓存(Cache):是计算机领域非常通用的概念。它介于应用程序永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
      缓存:程序 <-- (内存) --> 硬盘

    3.1.2、什么是二级缓存?

    • Hibernate 提供缓存机制:一级缓存、二级缓存。
      一级缓存:Session 级别缓存,在一次请求中共享数据。(当前有多少个线程连接到数据库,就会有多少个一级缓存。)
      二级缓存:SessionFactory 级别缓存,整个应用程序共享一个会话工厂,共享一个二级缓存。(二级缓存中存放的是经常使用的、不经常被修改的数据。)
    • SessionFactory的缓存两部分:
      内置缓存:使用一个Map,用于存放配置信息,如预定义的HQL语句等,提供给Hibernate框架自己使用,对外只读。不能操作。
      外置缓存:使用另一个Map,用于存放用户自定义数据。默认不开启。对于外置缓存,Hibernate只提供规范(接口),需要第三方实现类,所以我们使用二级缓存,还得导入第三方的jar包。外置缓存又称为二级缓存。

    3.1.3、二级缓存的内部结构

    • 二级缓存就是由4部分构成:
        类级别缓存
        集合级别缓存
        时间戳缓存
        查询级别缓存(二级缓存的第2大部分:三级缓存)
    • 内部结构如下所示:

    3.1.4、并发访问策略

    • 访问策略:读写型(read-write)、只读型(read-only)

    3.1.5、应用场景

    • 适合放入二级缓存中的数据:
      很少被修改
      不是很重要的数据,允许出现偶尔的并发问题
    • 不适合放入二级缓存中的数据:
      经常被修改
      财务数据,绝对不允许出现并发问题
      与其他应用数据共享的数据

    3.1.6、二级缓存提供商(即实现了二级缓存接口的厂商)

    • EHCache:可作为进程(单机)范围内的缓存,存放数据的物理介质可以是内存或硬盘,对 Hibernate 的查询缓存提供了支持。且支持集群。
    • OpenSymphony:可作为进程范围内的缓存,存放数据的物理介质可以是内存或硬盘。提供了丰富的缓存数据过期策略,对 Hibernate 的查询缓存提供了支持。
    • SwarmCache:可作为集群范围内的缓存,但不支持 Hibernate 的查询缓存。
    • JBoss Cache:可作为集群范围内的缓存,支持 Hibernate 的查询缓存。

    3.2、配置

      配置即操作:亦即使用二级缓存提供商的提供的jar。

    • 1、导入jar包并添加至构建路径:ehcache-1.5.0.jar(核心包)、commons-logging.jar(依赖包)、backport-util-concurrent.jar(依赖包)
    • 2、开启二级缓存(我要使用二级缓存)
    • 3、确定二级缓存提供商(我要使用哪个二级缓存)
    • 4、确定需要缓存内容
        (1)配置需要缓存的类
        (2)配置需要缓存的集合
    • 5、配置ehcache的自定义配置文件

    3.2.1、导入jar包并添加至构建路径

    3.2.2、开启二级缓存(我要使用二级缓存)

    先在 hibernate.properties 中找到对应的键和值:


    再在 hibernate.cfg.xml 中配置开启二级缓存:

    3.2.3、确定二级缓存提供商(我要使用哪个二级缓存)

    先在 hibernate.properties 中找到对应的键和值:


    再在 hibernate.cfg.xml 中配置确定二级缓存提供商:

    3.2.4、确定缓存内容

    在 hibernate.cfg.xml 中确定 类级别缓存集合级别缓存 配置项:
    先确定这两个缓存所在配置文件中的位置:


    具体配置:

    3.2.5、ehcache配置文件

    • 步骤1:从jar包复制 ehcache-failsafe.xml 文件
    • 步骤2:将 ehcache-failsafe.xml 重命名 ehcache.xml
    • 步骤3:将修改后的 ehcache.xml,拷贝到src下
    • 步骤4:删除掉 ehcache.xml 文件中无用的注释,得到清爽的 ehcache.xml 文件,文件内容如下:

    ehcache.xml

     <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <diskStore path="java.io.tmpdir"/>
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="true"
                maxElementsOnDisk="10000000"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"
                />

    </ehcache>

      一般,该文件的配置不是我们的工作,我们使用默认的配置即可,如果想配置,请看下文的 3.4、ehcache配置文件详解

    3.3、演示

    3.3.1、证明二级缓存存在

      示例代码如下:见类缓存中的示例代码

    3.3.2、类缓存

    • 类缓存:只存放数据
    • 一级缓存:存放对象本身
      如下图所示:

    示例代码如下:

    // 演示:二级缓存
    public class Demo1 {
        @Test
        // 演示:证明二级缓存存在 + 二级缓存中的类缓存
        public void fun1() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            // 证明二级缓存存在
            Customer c1 = (Customer) session.get(Customer.class1); // 有select语句
            session.clear(); // 清空一级缓存中的内容
            Customer c2 = (Customer) session.get(Customer.class1); // 没有select语句

            // 二级缓存中的类缓存
            System.out.println(c1 == c2); // false
            // 由以上说明,二级缓存中的类缓存在缓存数据时,并不是以对象的形式进行缓存的,而是缓存的是对象数据的散列,每次从二级缓存中取出数据,会在类缓存中组装成对象,并返回对象。

            session.getTransaction().commit();
            session.close();
        }
    }

    3.3.3、集合缓存

    如下图所示01:


    如下图所示02:

    示例代码如下:

    // 演示:二级缓存操作
    public class Demo2 {
        @Test
        // 演示:二级缓存中的集合缓存
        public void fun1() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            Customer c1 = (Customer) session.get(Customer.class1); // 一条 select 语句,查客户,是类
            for (Order o : c1.getOrders()) { // 一条 select 语句,查客户的订单,是集合
                System.out.println(o.getOname());
            }

            session.clear(); // 清空一级缓存

            // 二级缓存中的集合缓存
            Customer c2 = (Customer) session.get(Customer.class1); // 没有select语句
    //        // 增强for遍历
    //        for (Order o : c1.getOrders()) { // 没有select语句
    //            System.out.println(o.getOname());
    //        }
            // 迭代器遍历
            Iterator<Order> it = c2.getOrders().iterator(); // 没有select语句
            while (it.hasNext()) {
                Order o = it.next();
                System.out.println(o.getOname());
            }
            // 如果在使用二级缓存的集合缓存的时候,如果把集合对应的类缓存没有配置上去的话,那么在遍历集合中的元素的时候,select语句会发送很多次。
            // 由上可知,二级缓存中的集合缓存中放的是对象的OID,每次从二级缓存中取出数据时,会根据IOD先从类缓存中查找OID对应的数据,如果没找到,会拿着OID从数据库中找。

            session.getTransaction().commit();
            session.close();
        }
    }

    3.3.4、查询缓存

    • 查询缓存又称为三级缓存(民间叫法)
    • 查询缓存默认不使用。需要手动开启。
    • 查询缓存:将HQL语句与查询结果进行绑定。通过HQL相同语句可以缓存内容。
      默认情况Query对象只将查询结果存放在一级和二级缓存中,不从一级或二级缓存中获取。
      查询缓存就是让Query可以从二级缓存中获得内容。

    步骤一:开启查询缓存
    先在 hibernate.properties 中找到对应的键和值:


    再在 hibernate.cfg.xml 中配置开启查询缓存:

    步骤二:在查询query对象时,需要设置缓存内容(注意:存放和查询 都需要设置

    示例代码如下:

    // 演示:二级缓存操作
    public class Demo3 {
        @SuppressWarnings({ "unused""unchecked" })
        @Test
        // 演示:二级缓存中的查询缓存
        // 查询缓存是对hql语句查询的缓存
        public void fun1() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            Query query = session.createQuery("from Customer");
            // 使用二级缓存中的查询缓存时,需要单独再打开
            query.setCacheable(true); // 查询时,会先从二级缓存中取结果,取不到就执行语句,将执行结果放入二级查询缓存中
            List<Customer> list = query.list(); // 一条 select 语句

            session.clear(); // 清空一级缓存

            Query query2 = session.createQuery("select c from Customer c");
            query2.setCacheable(true);
            List<Customer> list2 = query2.list(); // 没有select语句

            session.getTransaction().commit();
            session.close();
        }
    }

    3.3.5、时间戳缓存

    • 时间戳:任何操作都在时间戳中记录操作时间。

    示例代码如下:

    // 演示:二级缓存操作
    public class Demo4 {
        @SuppressWarnings("unused")
        @Test
        // 演示:时间戳缓存
        public void fun1() {
            Session session = HibernateUtils.openSession();
            session.beginTransaction();

            Customer c1 = (Customer) session.get(Customer.class1); // 一条 select 语句
            session.createQuery("update Customer set cname=:cname where cid =:cid").setString("cname""rose").setInteger("cid"1).executeUpdate(); // 使用HQL绑定参数
            session.clear(); // 清空一级缓存
            Customer c2 = (Customer) session.get(Customer.class1); // 又一条 select 语句,因为所有的操作都会在时间戳中进行记录,如果数据不一致(时间戳不一样),将触发select语句进行查询

            session.getTransaction().commit();
            session.close();
        }
    }
    • 二级缓存中类缓存、集合缓存、查询缓存、时间戳缓存的总结图:

    3.4、ehcache配置文件详解

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
        <diskStore path="java.io.tmpdir"/>
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="true"
                maxElementsOnDisk="10000000"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"
                />
    </ehcache>

    <!-- 
        <diskStore path="java.io.tmpdir"/>  设置临时文件存放位置。(缓存一般写入内存,一定程度时,写入硬盘。)

        缓存详细设置
        <defaultCache />    所有的缓存对象默认的配置
        <cache name="类">    指定对象单独配置

        参数设置
            maxElementsInMemory="10000"     内存最大数(类内存中存储对象数据的散列的最大数)
            eternal="false"                 是否永久(内存常驻留)
            timeToIdleSeconds="120"         对象在内存中最多空闲多少秒
            timeToLiveSeconds="120"         对象在内存中最多存活多少秒
            overflowToDisk="true"           内存满了,是否写入到硬盘
            maxElementsOnDisk="10000000"    硬盘最大数(硬盘中存储对象数据的散列的最大数)
            diskPersistent="false"          关闭JVM,是否将内存保存硬盘中
            diskExpiryThreadIntervalSeconds="120"  轮询
            memoryStoreEvictionPolicy="LRU"
                Least Recently Used (specified as LRU)
                First In First Out (specified as FIFO) 
                Less Frequently Used (specified as LFU)

            •maxElementsInMemory    设置基于内存的缓存中可存放的对象最大数目 
            •eternal                设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性,默认值是false 
            •timeToIdleSeconds      设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。 
            •timeToLiveSeconds      设置对象生存最长时间,超过这个时间,对象过期。
                                    如果此值为0,表示对象可以无限期地存在于缓存中。该属性值必须大于或等于 timeToIdleSeconds属性值。
            •overflowToDisk         设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 。
            •diskPersistent         当jvm结束时是否持久化对象,默认是false
            •diskExpiryThreadIntervalSeconds    指定专门用于清除过期对象的监听线程的轮询时间。
            •memoryStoreEvictionPolicy          当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。
                                                默认是LRU(移除最近最少使用的元素),可选的有LFU(移除最不常使用的元素)和FIFO(先进先出) 。
    -->

    四、Hibernate小案例

    4.1、项目流程

      如下图所所示:在4.2中

    4.2、创建数据库和数据库表设计

    如下图所所示:

    4.3、创建实体模型

    如下图所所示:

    4.4、制定技术架构:Struts2 + Hibernate

    如下图所所示:

    4.5、搭框架

    如下图所所示:

    4.6、编写代码

    4.6.1、先将准备好的静态页面拷贝到 WebRoot 目录下


    项目中的位置:

      将静态的 listTopics.htmlshowTopic.html 文件改为动态的 listTopics.jspshowTopic.jsp 文件

    4.6.2、编写代码

    发帖图解:


    根据条件查询所有帖子列表图解:

    点击主题,显示对应帖子内容和所有回帖内容图解:
    回帖回复以及显示图解:
    不在赘述,且看详细代码。


    示例代码如下:
    TopicDao.java

    public interface TopicDao {
        /**
         * 保存帖子
         * 
         * @param t
         */

        void save(Topic t);

        /**
         * 根据条件查询帖子列表
         * 
         * @param condition
         * @return
         */

        List<Topic> getAll(String condition);

        /**
         * 根据帖子id查询帖子
         * 
         * @param tid
         * @return
         */

        Topic getTopic(Integer tid);

        /**
         * 保存回帖
         * 
         * @param r
         */

        void saveReply(Reply r);
    }

    TopicDaoImpl.java

    public class TopicDaoImpl implements TopicDao {

        @Override
        public void save(Topic t) {
            Session session = HibernateUtils.getCurrentSession();
            session.save(t);
        }

        @Override
        public List<Topic> getAll(String condition) {
            // 1、获取session对象
            Session session = HibernateUtils.getCurrentSession();
            // 2、创建query对象
            Query query = session.createQuery("from Topic where title like :condition");
            // 3、 设置参数,拼接sql语句
            if (condition != null && !"".equals(condition.trim())) {
                query.setString("condition""%" + condition + "%"); // 模糊查询,匹配的是帖子标题
            } else {
                query.setString("condition""%"); // 模糊查询
            }
            // 4、执行list
            return query.list();
        }

        @Override
        public Topic getTopic(Integer tid) {
            // 1、获取session对象
            Session session = HibernateUtils.getCurrentSession();
            // 2、创建query对象
            Query query = session.createQuery("from Topic where tid=:tid");
            // 3、 设置参数,拼接sql语句
            query.setInteger("tid", tid);
            // 4、执行list
            return (Topic) query.uniqueResult(); // 查询一条数据
        }

        @Override
        public void saveReply(Reply r) {
            Session session = HibernateUtils.getCurrentSession();
            session.save(r);
        }
    }

    TopicAction.java

    public class TopicAction extends ActionSupport{

        private Topic topic;
        private TopicDao td = new TopicDaoImpl();

        public Topic getTopic() {
            return topic;
        }
        public void setTopic(Topic topic) {
            this.topic = topic;
        }
        // 发帖子
        public String add() {
            // 1、手动添加ip地址(作者/发帖人)
            topic.setIpAddr(ServletActionContext.getRequest().getRemoteAddr());
            // 2、手动添加发帖时间(创建时间)
            topic.setCreateDate(new Date());
            // 3、手动添加最后回帖时间
            topic.setLastReplyDate(topic.getCreateDate());
            // 4、 调用dao保存帖子
            td.save(topic);
            // 5、重定向到帖子列表页面
            return "listTopic";
        }

        private String condition;

        public String getCondition() {
            return condition;
        }
        public void setCondition(String condition) {
            this.condition = condition;
        }
        // 显示帖子列表
        public String list() {
            // 1、调用dao获得帖子列表 (查询结果)
            List<Topic> list = td.getAll(condition);
            // 2、将帖子列表(查询结果)放入ActionContext中
            ActionContext.getContext().put("list"list); // 把数据直接存到了大Map中,其key的值为list
            // 3、转发至list页面进行显示
            return "list";
        }

        private Integer tid;

        public Integer getTid() {
            return tid;
        }
        public void setTid(Integer tid) {
            this.tid = tid;
        }
        // 根据帖子id显示帖子内容+回帖内容
        public String getTopicById() {
            // 1、调用dao获得帖子 (查询结果)
            Topic topic = td.getTopic(tid);
            // 2、将帖子(查询结果)放入ActionContext中
            ActionContext.getContext().put("topic_show", topic); // 把数据直接存到了大Map中,其key的值为topic
            // 3、转发至show页面进行显示
            return "show";
        }

        private Reply reply;

        public Reply getReply() {
            return reply;
        }
        public void setReply(Reply reply) {
            this.reply = reply;
        }
        // 回复帖子+显示回帖
        public String replyTopic() {
            // 1、手动添加ip地址(作者/发帖人)
            reply.setIpAddr(ServletActionContext.getRequest().getRemoteAddr());
            // 2、手动添加回帖时间(创建时间)
            reply.setCreateDate(new Date());
            // 3、手动设置回帖与帖子的关联关系
            reply.setTopic(td.getTopic(tid));
            // 4、 调用dao保存回帖
            td.saveReply(reply);

            // 调用dao获得帖子 (查询结果)
            Topic topic = td.getTopic(tid);
            // 将帖子(查询结果)放入ActionContext中
            ActionContext.getContext().put("topic_show", topic); // 把数据直接存到了大Map中,其key的值为topic
            // 转发至show页面进行显示
            return "show";
        }
    }

    listTopic.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib uri="/struts-tags" prefix="s"%>

    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>


    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <title>主题列表</title>
        <link rel="stylesheet" type="text/css" href="css/main.css">
    </head>
    <body>

        <!-- 简单搜索表单 -->
        <div style="margin: 15px auto; " >
            <!-- 搜索表单 -->
            <s:form action="topicAction_list" namespace="/" theme="simple" cssClass="simpleSearchForm">
            <!-- <form action="" class="simpleSearchForm" onsubmit="alert('暂不支持此功能!');return false;"> -->
                <font class="logoLabel">贴吧</font>
                <s:textfield name="condition" cssClass="queryString"></s:textfield>
                <!-- <input type="text" name="queryString" class="queryString"/> -->
                <input type="submit" value="搜 索" />
            <!-- </form> -->
            </s:form>
        </div>

        <!-- 菜单 -->
        <div class="menubar">
            <s:a action="topicAction_list" namespace="/">主题列表
            <!-- <a href="listTopic.jsp">主题列表</a> -->
            </s:a>
        </div>

        <!-- 主题列表 -->
        <table cellspacing="0">
            <tbody class="list topicList">
                <!--显示表头-->
                <tr class="title">
                    <td width="25">编号</td>
                    <td width="25">回复</td>
                    <td width="500">主题</td>
                    <td width="110">作者</td>
                    <td width="145">最后回复时间</td>
                </tr>
            <s:iterator value="#list" var="t"><!-- 写了var属性,就在大Map中找 -->
                <!-- 显示帖子列表 -->
                <tr class="data">
                    <td class="num"><s:property value="#t.tid"/></td>
                    <td class="num"><s:property value="#t.replySet.size"/></td>
                    <td>
                        <s:a action="getTopicById" namespace="/">
                        <!-- <a href="showTopic.jsp"> -->
                            <s:param name="tid" value="#t.tid"></s:param>
                            <s:property value="#t.title"/>
                        <!-- </a> -->
                        </s:a>
                    </td>
                    <td class="info"><s:property value="#t.ipAddr"/></td>
                    <td class="info"><s:date name="#t.createDate" format="yyyy-MM-dd hh:mm:ss"/></td>
                </tr>
            </s:iterator>
                <tr>
                    <td colspan="5" class="num">共有主题数<font color="red"><s:property value="#list.size"/></font></td>
                </tr>
                </tbody>
        </table>

        <div style="margin-bottom: 15px"></div>

        <!--发表主题表单-->
        <s:form action="topicAction_add" namespace="/" theme="simple" cssClass="addNewTopicForm">
        <!-- <form action="" class="addNewTopicForm">-->
            <table class="publishArticleForm">
                <tr>
                    <td>标 题:</td>
                    <td>
                        <s:textfield name="topic.title" cssClass="title"/>
                        <!-- <input type="text" class="title"/> -->
                    </td>
                </tr>
                <tr>
                    <td>内 容:</td>
                    <td>
                        <s:textarea name="topic.topicContent" cssClass="topicContent"></s:textarea>
                        <!-- <textarea name="content" class="content"></textarea> -->
                    </td>
                </tr>
                <tr>
                    <td></td>
                    <td>
                        <input type="submit" value="发 表" />
                    </td>
                </tr>
            </table>
        <!-- </form> -->
        </s:form>
    </body>
    </html>

    showTopic.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib uri="/struts-tags" prefix="s"%>

    <%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    %>


    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <title>主题<s:property value="#topic_show.title"/>的回帖专区</title>
        <link rel="stylesheet" type="text/css" href="css/main.css"> 
    </head>
    <body>
        <!-- 简单搜索表单 -->
        <div style="margin: 15px auto; " >
            <!-- 搜索表单 -->
            <s:form action="topicAction_list" namespace="/" theme="simple" cssClass="simpleSearchForm">
            <!-- <form action="" class="simpleSearchForm" onsubmit="alert('暂不支持此功能!');return false;"> -->
                <font class="logoLabel">贴吧</font>
                <s:textfield name="condition" cssClass="queryString"></s:textfield>
                <!-- <input type="text" name="queryString" class="queryString"/> -->
                <input type="submit" value="搜 索" />
            <!-- </form> -->
            </s:form>
        </div>

        <!-- 菜单 -->
        <div class="menubar">
            <s:a action="topicAction_list" namespace="/">主题列表
            <!-- <a href="listTopic.jsp">主题列表</a> -->
            </s:a>
        </div>

        <!-- 当前主题贴数 -->
        <div style="padding: 10px 30px; font-size: 12px; font-family:'宋体'">
            共有<font color="red"><s:property value="#topic_show.replySet.size"/></font>篇回帖
        </div>

        <!-- 显示主题 -->
        <table class="postList" cellspacing="0">
            <tr class="title">
                <td width="20" class="num">1</td>
                <td><s:property value="#topic_show.title"/></td>
            </tr>
            <tr class="content">
                <td></td>
                <td><pre><s:property value="#topic_show.topicContent"/></pre></td>
            </tr>
            <tr class="info">
                <td></td>
                <td>
                    作者:<font color="blue"><s:property value="#topic_show.ipAddr"/></font> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                    <font color="#999999">发帖时间:<s:date name="#topic_show.createDate" format="yyyy-MM-dd hh:mm:ss"/></font>
                </td>
            </tr>
        </table>

    <!-- 显示回复列表 -->
    <s:iterator value="#topic_show.replySet" var="ts" status="vs">
        <table class="postList" cellspacing="0">
            <tr class="title">
                <td width="20" class="num"><s:property value="#vs.count+1"/></td>
                <td></td>
            </tr>
            <tr class="content">
                <td></td>
                <td><pre><s:property value="#ts.replyContent"/></pre></td>
            </tr>
            <tr class="info">
                <td></td>
                <td>
                    作者:<font color="blue"><s:property value="#ts.ipAddr"/></font> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
                    <font color="#999999">回帖时间:<s:date name="#ts.createDate" format="yyyy-MM-dd hh:mm:ss"/></font>
                </td>
            </tr>
        </table>
    </s:iterator>

        <div style="margin-bottom: 20px"></div>
        <!-- 发表回复表单 -->
        <s:form action="replyTopic" namespace="/" theme="simple" cssClass="addNewTopicForm">
        <!-- <form action="" class="addNewTopicForm"> -->
        <s:hidden name="tid" value="%{#topic_show.tid}"></s:hidden>
            <table class="publishArticleForm">
                <tr>
                    <td class="label">内 容:</td>
                    <td>
                        <s:textarea name="reply.replyContent" cssClass="content"></s:textarea>
                        <!-- <textarea name="content" class="content"></textarea> -->
                    </td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="回 贴"/></td>
                </tr>
            </table>
        <!-- </form> -->
        </s:form>
    </body>
    </html>

    效果截图:


    搜索帖子截图:

    回帖显示截图:

    4.7、小问题解决

    问题1、当我们没有整合log4j框架时,控制台出现如下异常:

    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    七月 162018 5:23:14 下午 org.apache.catalina.core.StandardWrapperValve invoke
    严重: Servlet.service() for servlet [jsp] in context with path [/day32_05_Hibernate_tieba] threw exception [Filter execution threw an exceptionwith root cause
    org.hibernate.MappingException: column attribute may not be used together with <column> subelement
        at org.hibernate.cfg.HbmBinder.bindColumns(HbmBinder.java:1136)
        at org.hibernate.cfg.HbmBinder.bindColumnsOrFormula(HbmBinder.java:1628)
        at org.hibernate.cfg.HbmBinder.bindSimpleValue(HbmBinder.java:1208)
        at org.hibernate.cfg.HbmBinder.createClassProperties(HbmBinder.java:2206)
        at org.hibernate.cfg.HbmBinder.createClassProperties(HbmBinder.java:2164)
        at org.hibernate.cfg.HbmBinder.bindRootPersistentClassCommonValues(HbmBinder.java:412)
        at org.hibernate.cfg.HbmBinder.bindRootClass(HbmBinder.java:326)
        at org.hibernate.cfg.HbmBinder.bindRoot(HbmBinder.java:177)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processHbmXml(Configuration.java:4006)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processHbmXmlQueue(Configuration.java:3998)
        at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3986)
        at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1398)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1856)
        at com.itheima.utils.HibernateUtils.<clinit>(HibernateUtils.java:19)
        at com.itheima.web.filter.TransactionFilter.doFilter(TransactionFilter.java:27)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at com.itheima.web.filter.EncodingFilter.doFilter(EncodingFilter.java:41)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:651)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:407)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:754)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1376)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)

    因为SLF4J 可以根据我们选择的绑定把日志输出到 log4j 日志框架上,所以我们需要整合log4j,步骤如下:

    • 导入jar包:
      • 1、log4j 核心包:log4j-1.2.17.jar
      • 2、过渡jar(整合jar):slf4j-log4j12-1.7.5.jar
    • 导入配置文件,该文件 log4j.properties 在 hibernate-distribution-3.6.10.Finalprojectetc 中,拷贝至项目的src 目录下
    • 使用该配置文件的默认配置即可,不需要修改里面的配置

    整合log4j后,异常没有了。

    问题2、提交发帖表单时,浏览器出现500错误, Message java.util.Date cannot be cast to java.util.Calendar
    如下图所示:


    原因是 Reply.hbm.xml 和 Toopic.hbm.xml 配置文件中的
        <property name="createDate" column="createDate" type="calendar"></property>

    数据类型与数据库中对应的数据类型不符。
      解决办法一:可以把数据库中对应的数据类型改为calendar。
      解决办法二:可以把该配置文件中这个属性 type="calendar" 删除掉,让数据库它自动识别。
      解决办法三:单独设置该列的属性,直接配置成数据库类型(一劳永逸式),代码如下:

        <property name="createDate">
            <column name="createDate" sql-type="datetime"></column>
        </property>

    再次提交页面,没有问题了。

  • 相关阅读:
    笔记:Oracle SQL 高级编程 第2章 SQL 执行
    python 中的 filter, lambda, map, reduce 内置函数
    笔记:Oracle SQL 高级编程 第1章 SQL 核心
    java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射
    使用JDBC进行批处理
    程序员面试、算法研究、编程艺术、红黑树、数据挖掘5大系列集锦
    教你如何迅速秒杀掉:99%的海量数据处理面试题
    十道海量数据处理面试题与十个方法大总结
    《Java 7 并发编程指南》学习概要 (7) 定制并发类
    HashMap多线程并发问题分析
  • 原文地址:https://www.cnblogs.com/chenmingjun/p/9315610.html
Copyright © 2011-2022 走看看