ssh框架

课程安排:spring、struts2、hibernate、spring mvc、mybaits—>开源的项目

  1. 安全性框架:shiro、spring security---->自定义的标签(jsp)

servlet---->spring mvc
filer------>struts2
listener—>监听器
1.mybatis:学习成本较低,sql语句,半自动化的orm框架
jdbc—
2.hibernate:学习成本较高,面向对象orm,全自动的orm框架,hql(查询)
spring:轻量级的框架,当容器看待,提供一些服务(事务(ACID)服务、消息服务、缓存服务等)

maven(apache):项目构建、项目管理的工具
    环境搭建
    安装jdk
    下载maven
    解压-->配置环境变量

    添加国内镜像

    <mirror>
        <id>nexus-aliyun</id>
        <mirrorOf>*</mirrorOf>
        <name>Nexus aliyun</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror> 

    配置本地仓库

    初始化-->执行一条命令

    mvn help:system

    好处:
    自动下载依赖-->从maven的中央仓库(国外)--->缓存本地仓库

创建第一个maven项目

    项目结构

    src/main/java-------->存放java源文件
    src/main/resources--->资源目录,存放配置文件
    src/test/java-------->单元测试
    src/test/resources

    target目录--->最终项目打包之后项目存放的文件夹
    pom.xml--->maven的核心文件
        build--->编译
        dependences--->依赖

    添加依赖-->http://mvnrepository.com/

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

maven中常用的命令
    编译:mvn compile
    单元测试:mvn test
    打包:mvn package
    清除:mvn clean-->清除掉target中的所有文件

struts2

struts2--->apache
    介绍--->基于mvc设计模式的框架-->filter的包装
    基于action的框架
    基于xwork的架构

    环境搭建
        引入jar包

        配置前端控制器-->web.xml中配置

        <!-- 配置struts的前端控制器 -->
        <filter>
            <filter-name>struts2</filter-name>
            <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>struts2</filter-name>
            <url-pattern>*.action</url-pattern>
        </filter-mapping>

        编写struts.xml配置文件(struts的核心配置文件)

        <!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
        "http://struts.apache.org/dtds/struts-2.3.dtd">
        <struts>

        </struts>

        引入log4j

        <!-- 引入log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>

        添加log4j.properties

        ### direct log messages to stdout ###
        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
        log4j.appender.stdout.Target=System.out
        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
        log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

        log4j.rootLogger=debug, stdout

    获取用户输入-->模拟用户登录
        编写action(接口)-->直接和用户打交道-->处理用户的请求
            实现action接口
            继承Actionsupport---->常用
            编写一个普通的java类

        action获取用户提交的数据
            action属性
                属性的名称和用户提交的参数名称保持一致
                具有setter/getter

            领域对象-->定义一个普通的pojo对象
                定义pojo,属性名和提交参数名保持一致,具有getter/setter
                action中声明该pojo类型的属性

                界面修改

                <td><input type="password" name="user.password"></td>

            modeldriven(接口)--->拦截器注入数据--->推荐使用的

                实现modelDriven接口

                public class LoginAction implements ModelDriven<User> {

                    private User user;

                    @Override
                    public User getModel() {
                        user = new User();
                        return user;
                    }

                界面

                <tr>
                    <td>用户名:</td>
                    <td><input type="text" name="userName"></td>
                </tr>
                <tr>
                    <td>密码:</td>
                    <td><input type="password" name="password"></td>
                </tr>
                <tr>
                    <td colspan="2" align="center">
                        <input type="submit" value="登录">
                        <input type="reset" value="重置">
                    </td>
                </tr>

        struts中的url格式:namespace+"/"+actionName

        <form action="my/UserAction.action" method="post">

    和servlet解耦

        需求--->success.jsp显示登陆用户的信息?
            servlet--->将数据放到域(page、request、session、application)

            struts--->中
                和servlet解耦的--->运行不依赖与servlet了

                ActionContext

                ActionContext actionContext = ActionContext.getContext();//request域
                actionContext.put("user", user);
                Map<String, Object> session = actionContext.getSession();//session域
                session.put("user1", user);
                Map<String, Object> application = actionContext.getApplication();//application域
                application.put("user2", user);

                通过struts提供的拦截器注入相应的域对象--->推荐使用
                    通过实现不同的接口:RequestAware,SessionAware,ApplicationAware

                不解耦的方式

                ServletContext servletContext = ServletActionContext.getServletContext();//application
                HttpServletRequest request2 = ServletActionContext.getRequest();//request
                HttpSession session2 = request2.getSession();//session

    调用action中的不同的方法

        动态方法调用--->官网不推荐使用

            配置一个常量

            <!-- 允许动态方法调用
                aciontName!methodName.action
             -->
            <constant name="struts.enable.DynamicMethodInvocation" value="true"/>

            url地址

            aciontName!methodName.action

        采用方法通配符"*"的

        <!-- 配置UserAction -->
        <action name="UserAction_*" class="com.woniu.struts.action.UserAction" method="{1}">
            <result name="success">/success.jsp</result>
            <result name="fail">/fail.jsp</result>
        </action>

    方法校验-->执行某个方法之前首先会执行校验的方法
        需求--->用户注册:数据格式和长度等要求

        实现
            action实现Validateable接口或者继承actionSupport类

            重写validate方法

            @Override
            public void validate() {    //全方法校验:该action中的所有的方法都会进行方法校验
                //实现自己的校验规则
                if(user!=null&&user.getPassword().length()<6){
                    //校验失败
                    addActionError("密码必须长度大于6");//--->返回到一个逻辑试图名为input的视图
                }
            }

            public void validateRegister() {//只校验方法名为register的方法
                //实现自己的校验规则
                if(user!=null&&user.getPassword().length()<6){
                    //校验失败
                    addActionError("密码必须长度大于6");//--->返回到一个逻辑试图名为input的视图
                }
            }

                界面

                    引入标签库

                    <!-- 引入struts的标签库 -->
                    <%@ taglib uri="/struts-tags" prefix="s" %>

                    使用标签显示错误信息

                    <td>
                        <input type="password" name="password">
                        <!-- struts中的所有标签都必须经过filter:及修改web.xml文件中filter的匹配格式为/* -->
                        <s:actionerror/>
                    </td>

    struts.xml配置文件详解

        常量

        name:常量的名称--->/org/apache/struts2/default.properties文件中定义好的
        <constant name="struts.enable.DynamicMethodInvocation" value="false"/>

            动态方法调用:struts.enable.DynamicMethodInvocation
            主题相关的:struts.ui.theme
            struts.devMode--->开发的时候建议打开

            后缀:struts.action.extension--->默认值为action

            <constant name="struts.action.extension" value="go,do"/>

            文件上传相关

            struts.multipart.maxSize=2097152  :文件上传的最大值 2M

            对象的创建和维护

            struts.objectFactory = spring

        include--->引入外部的配置文件

        1. 指定的配置文件
        <include file="struts_a.xml"/>
        <include file="struts_b.xml"/>

        2. 通过统配的方式
        <include file="struts_*.xml"/>

        package:管理所有的action的
            属性说明
                name:包的名称,唯一性,只用于继承(继承父类定义的所有的常量、拦截器、全局的视图)
                namespace:命令空间,唯一性,url地址的组成的部分
                extends:继承至那个包,将继承父包中定义的action、拦截器、全局视图
                abstract:该包为一个抽象的包,不能有action的配置
            注意事项
                抽象包:不能有action的配置
        action:对应自己编写的action在struts配置
            属性说明
                name:action的名称,唯一性,url的组成的部分
                class:自己编写的action的权限定名
                method:调用的action中的某个方法的名称
            注意事项
                默认的action:加入class没有指定,默认调用的ActionSupport
                默认的方法:加入method为空,将调用该action中execute方法

        result:逻辑视图和视图(真实的界面)的一个对应关系

            属性
                name:逻辑视图名,和action的方法的放回值保持一致

                type:返回视图的类型

                    常用的类型
                        dispatcher:请求转发的方式,struts默认的视图的返回类型
                        plainText:输出text文件,源文件输出
                        redirect:重定向

                        redirectAction:

                        <result name="redirectAction" type="redirectAction">
                            <param name="namespace">/my</param>
                            <param name="actionName">UserAction_doLogin</param>
                        </result>

                        说明:重定向到的action同一个包中,那么namespace省略不写

                        <result name="redirectAction" type="redirectAction">UserAction_doLogin</result>

                        stream:以二进制的数据返回

                    返回json数据

                        引入json的插件

                        <dependency>
                            <groupId>org.apache.struts</groupId>
                            <artifactId>struts2-json-plugin</artifactId>
                            <version>2.3.29</version>
                        </dependency>

                        result的type指定返回json数据

                        <result name="json" type="json">
                            <!-- 转换成json的数据来源,当前action中招getUsers的方法 -->
                            <param name="root">users</param>
                            <!-- 编码 -->
                            <param name="encoding">UTF-8</param>
                        </result>

                        public String json(){
                            return "json";
                        }

                        public List<User> getUsers(){
                            List<User> result=new ArrayList<User>();
                            User user;
                            for(int i=0;i<5;i++){
                                user=new User();
                                user.setUserName("第"+i+"个数据");
                                result.add(user);
                                user=null;
                            }
                            return result;
                        }

                内容:真实的界面(view)

            注意事项
                默认的视图:result的name属性默认值为success

                全局视图:

                <!-- 全局视图:对所有的action都是有效的 -->
                <global-results>
                    <result name="input">/fail.jsp</result>
                </global-results>

    数据类型转换器

        使用
            继承DefaultTypeConverter类

            重新方法

            /**
             * @param value 待转换的数据
             * @param toType 转换成的数据类型
             */
            @Override
            public Object convertValue(Object value, Class toType) {
                try {
                    if(toType==Date.class){
                        SimpleDateFormat format=new SimpleDateFormat("yyyy/MM/dd");
                        return format.parse(((String[])value)[0].toString());
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return super.convertValue(value, toType);
            }

            注册数据类型转换器

                指定action:在Action类所在的包下放置ActionClassName-conversion.properties文件,ActionClassName是Action的类名,后面的-conversion.properties是固定写法,对于本例而言,文件的名称应为UserAction-conversion.properties 。在properties文件中的内容为: 属性名称=类型转换器的全类名 对于本例而言, UserAction-conversion.properties文件中的内容为:

                user.birthday=com.woniu.struts.convertor.MyDateConvertor                        

                全局:在WEB-INF/classes(及src)下放置xwork-conversion.properties文件 。在properties文件中的内容为: 待转换的类型=类型转换器的全类名 对于本例而言, xwork-conversion.properties文件中的内容为:

                java.util.Date=com.woniu.struts.convertor.MyDateConvertor

    文件上传

        引入依赖:

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>

        界面
            form表单提交方式必须为post请求

            enctype="multipart/form-data"

            <form action="" method="post" enctype="multipart/form-data">
                <input type="file" name="uploadFile">
                <input type="submit" name="上传文件">
            </form>

        action

            定义一个file类型的属性,属性名和input的name属性保持一致

            public class FileUploadAction {

                private File uploadFile;// 获取上传的文件
                private String uploadFileContentType;// 得到文件的类型
                private String uploadFileFileName;// 得到文件的名称
                ....
                //getter\setter
            }

            保存文件到本地

            public String upload() {
                String path = "E:/工作/蜗牛/代班/19期班/fileupload";
                // 将用户上传的文件保存本地服务器
                // 1.获取上传文件的后缀----?
                String extension = uploadFileFileName.substring(uploadFileFileName.lastIndexOf("."));
                // 2.构建新的文件名
                String fileName=UUID.randomUUID().toString()+extension;
                //3.构建保存的文件
                File file=new File(path, fileName);
                //4.保存文件
                uploadFile.renameTo(file);
                return "success";
            }

        如果是多个文件只需要改为数组即可

        public class FileUploadAction {

            private File[] uploadFile;// 获取上传的文件
            private String[] uploadFileContentType;// 得到文件的类型
            private String[] uploadFileFileName;// 得到文件的名称
            ....
            //getter\setter
        }

    文件下载

        通过result里面type返回stream类型的数据

        <action name="FileUploadAction_*" class="com.woniu.struts.action.FileUploadAction"
            method="{1}">
            <result name="success">/success.jsp</result>
            <result name="download" type="stream">
                <!-- 返回的数据的类型 -->
                <param name="contentType">application/octet-stream</param>
                <!-- 数据的来源,从当前的action中招一个返回值inputstream方法:getIs -->
                <param name="inputName">is</param>
                <!-- 文件的描述信息 -->
                <param name="contentDisposition">attachment;filename="document.jar"</param>
                <param name="bufferSize">1024</param>
            </result>
        </action>

        action

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String download() {
            return "download";
        }

        public InputStream getIs() {
            try {
                String path = "E:/工作/蜗牛/代班/19期班/fileupload";
                File file=new File(path, name);
                return new FileInputStream(file);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }

        界面

        <a href="${pageContext.request.contextPath }/mya/FileUploadAction_download.go?name=e083e29c-e58a-4d54-98f9-d87adedbb8bb.jar">下载</a>

    拦截器:核心组件
        struts中的参数绑定,文件上传等都是通过拦截器来处理的

        自定义拦截器

            实现interceptor接口

            public class Myinterceptor implements Interceptor {

                private static final long serialVersionUID = 1L;

                @Override
                public void destroy() {

                }

                @Override
                public void init() {

                }

                /**
                 * @param invocation :action的执行器
                 * @return 逻辑视图名
                 */
                @Override
                public String intercept(ActionInvocation invocation) throws Exception {
                    ActionProxy actionProxy = invocation.getProxy();//得到action的代理对象
                    String actionName = actionProxy.getActionName();
                    String namespace = actionProxy.getNamespace();
                    //actionProxy.execute();//指定action中的方法
                    String method = actionProxy.getMethod();
                    //公共资源
                    if("doLogin".equalsIgnoreCase(method)){
                        return invocation.invoke();
                    }

                    //系统资源
                    Map<String, Object> session = ActionContext.getContext().getSession();//得到session对象
                    Object user = session.get("user");
                    if(user!=null){//登陆过
                        return invocation.invoke();
                    }
                    return "login";
                }

            }

            在struts的配置文件中配置拦截器

            <!-- 配置拦截器 -->
            <interceptors>
                <interceptor name="Myinterceptor" class="com.woniu.struts.interceptor.Myinterceptor"/>
            </interceptors>

            使用拦截器

                在某个action中使用拦截器,丢失原来struts已有的拦截器的功能

                <action name="UserAction_*" class="com.woniu.struts.action.UserAction" method="{1}">
                <!-- 指定使用的拦截器 -->
                <interceptor-ref name="Myinterceptor"/>

                全局的拦截器:对所有的action都有效

                <!-- 配置拦截器 -->
                <interceptors>
                    <interceptor name="Myinterceptor" class="com.woniu.struts.interceptor.Myinterceptor"/>
                    <!-- 定义拦截器栈:管理拦截器 -->
                    <interceptor-stack name="myStack">
                        <!-- 注入拦截器的 -->
                        <interceptor-ref name="Myinterceptor"/>
                        <!-- struts自带的拦截器注入进来 -->
                        <interceptor-ref name="defaultStack"/>
                    </interceptor-stack>
                </interceptors>

                <!-- 默认的拦截器 -->
                <default-interceptor-ref name="myStack"/>

    标签库

        引入标签库

        <%@ taglib uri="/struts-tags" prefix="s" %>

        常用的标签
            数据标签
            控制标签
            表单标签

hibernate

持久层的orm框架,实体关系映射框架,全自动的orm框架,完全面向对象的。和数据库打交道的。跨数据库的。对jdbc的包装。

环境搭建

    引入依赖

    <!-- hibernate的核心依赖 -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.0.11.Final</version>
    </dependency>
    <!-- mysql的驱动包 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.21</version>
    </dependency>

    编写hibernate.cfg.xml配置文件,文件名必须为hibernate.cfg.xml

    <!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <!-- 添加jdbc的信息 -->
            <!-- 驱动 -->
            <property name="hibernate.connection.driver_class">org.gjt.mm.mysql.Driver</property>
            <!-- url -->
            <property name="hibernate.connection.url">jdbc:mysql:///snail19</property>
            <property name="hibernate.connection.username">admin</property>
            <property name="hibernate.connection.password">123456</property>
            <!-- 配置表的生成策略 -->
            <property name="hibernate.hbm2ddl.auto">update</property>
        </session-factory>
    </hibernate-configuration>

    编写实体类及映射文件

        实体

        public class User {

            private int id;
            private String name;
            private String address;
            getter/setter...

        编写映射文件:XXX.hbm.xml

        <!DOCTYPE hibernate-mapping PUBLIC 
            "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
        <hibernate-mapping>
            <class name="com.snail.hibernate.entity.User" table="users">
                <id name="id" column="id" type="int">
                    <generator class="identity"/>
                </id>

                <property name="name" column="userName" type="string"/>
                <property name="address" column="address" type="string"/>
            </class>
        </hibernate-mapping>

    在hibernate的配置文件中添加映射文件

    <!-- 添加映射文件 -->
    <mapping resource="mappings/User.hbm.xml"/>

    单元测试

    public class HelloHibernate {

        private Session session;

        @Before
        public void before() {
            //1.加载hibernate的配置文件
            Configuration configuration=new Configuration().configure();
            //2.创建sessionFactory对象
            SessionFactory sessionFactory = configuration.buildSessionFactory();
            session = sessionFactory.openSession();
        }

        @After
        public void after() {
            //7.关闭资源
            if(session!=null)
                session.close();
        }

        @Test
        public void saveUser(){
            //4.开启事务
            Transaction tx = session.beginTransaction();

            User user=new User();
            user.setName("张三");
            //5.完成持久化
            session.save(user);
            //6.提交事务
            tx.commit();
        }

        @Test
        public void get(){
            User user = session.get(User.class, 1);
            System.out.println(user);
        }

    }

session中常用的方法及对象的三种状态
    session中的方法
        save:添加
        persist:保存
        update:更新,根据id更新数据,全字段更新
        saveOrUpdate:保存或者更新
        get:通过id获取数据
        load:通过id获取数据
        delete:根据id删除数据
    对象状态
        持久态:该对象即在数据库中存在也和session有关联,一旦属性改变自动会更新数据库中的数据
        临时态:该对象及没在数据库也没和session关联,一般直接new创建
        挂起:数据库中存在但是和session没有关联

hibernate.cfg.xml配置文件详解

    数据库信息
        url
        driver
        username

        passwrod

        <property name="hibernate.connection.driver_class">org.gjt.mm.mysql.Driver</property>
        <!-- url -->
        <property name="hibernate.connection.url">jdbc:mysql:///snail19</property>
        <property name="hibernate.connection.username">admin</property>
        <property name="hibernate.connection.password">123456</property>

    表生成策略:hibernate.hbm2ddl.auto
        create:加入数据库中该表存在则先删除,再重新创建,执行完成之后表存在
        update:只做表结构的更新--->开发阶段
        create-drop:加入数据库中该表存在则先删除,再重新创建,执行完成之后表再删除表
        validate:只做表结构的校验--->上线的

    数据库方言:跨数据库的核心

    <!-- 设置数据库的方言 -->
    <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>

    映射文件-->最为核心文件,标识的hibernate中实体与数据库表结构的对应关系

    <mapping resource="mappings/User.hbm.xml"/> -->指定映射文件的地址

    其他

        缓存相关

        hibernate.cache.use_second_level_cache false    //是否允许二级缓存
        hibernate.cache.use_query_cache true            //是否允许查询缓存

        数据源

        #hibernate.c3p0.max_size 2
        #hibernate.c3p0.min_size 2
        #hibernate.c3p0.timeout 5000
        #hibernate.c3p0.max_statements 100
        #hibernate.c3p0.idle_test_period 3000
        #hibernate.c3p0.acquire_increment 2
        #hibernate.c3p0.validate false

        格式sql

        hibernate.show_sql  -->是否显示sql语句
        hibernate.format_sql--->格式sql语句

映射文件详解:实体类和表结构的对应关系

    主键

        主键生成策略
            native:采用本地数据库
            identity:自增,由数据库维护
            increment:自增,由hibernate维护
            uuid:唯一的字符串,数据库中的主键的数据类型就必须为字符串
            assigned:默认值,不使用任何生成策略,保存之前得先对主键赋值

            foreign:使用外键赋值,在一对一关系映射时可以使用

            <!-- 对数据库表中id的映射
                name:实体类中的属性的名称
                column:数据库表的id的名称
                type:java中的数据类型
             -->
            <id name="id" column="id" type="int">
                <!-- 主键的生成策略 -->
                <generator class="identity"/>
            </id>

    普通属性

    <!-- 
        name:实体类的属性名
        column:数据库表的列的名称
        type:java中的数据类型
        unique:该列添加唯一性约束
        not-null:非空约束
        length:数据的长度
     -->
    <property name="name" type="string">
        <!-- 
            sql-type: 表示数据库中的数据类型
            property的colum属性不能和colum节点一起使用
        -->
        <column name="userName" sql-type="varchar(500)">

        </column>
    </property>

    <!-- 
        formula:执行sql语句的,数据不来源于数据库中某一列而是来源sql语句执行之后的结果
     -->
    <property name="totalSize" type="int" formula="(SELECT count(*) from users)"/>

hql查询:hibernate query language-->面向对象的orm框架
    语法
        不能出现表名、列名,只能出现实体类的类名和属性名
        数据库的关键字区分大小写

    使用

        单表查询
            返回单个对象
            返回集合对象
            条件查询
                where
                支持的运算符

            统计查询

                @Test
                public void query() {
                    String hql="select u from User u";
                    //得到query对象
                    Query query = session.createQuery(hql);

                    //执行查询
                    List<User> users = query.list();//-->返回集合
                    for(User user:users){
                        System.out.println(user);
                    }
                }

                @Test
                public void queryUnique() {
                    String hql = "select count(u) from User u";
                    // 得到query对象
                    Query query = session.createQuery(hql);

                    Number number = (Number) query.uniqueResult();
                    System.out.println(number.intValue());
                }

                @Test
                public void queryEmployee() {
            //      String hql="select u from Employee u where age=:age";//使用命名参数
                    String hql="select u from Employee u where age=?";//使用占网费
                    //得到query对象
                    Query query = session.createQuery(hql);

                    //绑定数据
                    query.setInteger(0, 20);

                    int size = query.list().size();
                    System.out.println(size);
                }

        分页查询

        @Test
        public void queryPage(){
            String hql="from Employee";
            Query query = session.createQuery(hql);

            //执行分页
            query.setFirstResult(5);//offset,第一条数据从什么位置开始取
            query.setMaxResults(5);//size,取多条数据

            List<Employee> employees = query.list();
            System.out.println(employees);
        }

        跨表查询

        @Test
        public void queryEmployee1(){
            String hql="from Employee e where e.department.name=?";
            Query query = session.createQuery(hql);

            query.setString(0, "研发部");

            List<Employee> employees = query.list();
            System.out.println(employees);
        }

        执行写操作

        @Test
        public void updateUser(){
            String hql="UPDATE User SET address=? where id=?";

            Query query = session.createQuery(hql);

            query.setString(0, "成都");
            query.setInteger(1, 4);

            query.executeUpdate();//执行hql语句
        }

        执行原生的sql查询,

        @Test
        public void queryBySql(){
            String sql="SELECT * from employees";

            SQLQuery sqlQuery = session.createSQLQuery(sql);

            sqlQuery=sqlQuery.addEntity(Employee.class);

            //查询
            List list = sqlQuery.list();
            for(int i=0;i<list.size();i++){
                System.out.println(list.get(i));
            }

        }

            注意事项:映射文件中不能包含带计算的属性,否则执行的会报空指针异常

            <property name="totalSize" type="int" formula="(SELECT count(*) from users)"/> 

关系映射

1.数据库中表间关系,外键、主键
  1.一对一:
  2.一对多:部门--->员工(外键--->部门的id)
  3.多对多:拆分为两个一对多,产生一张中间表
2.hibernate中实体的关系:单边、双边
  1.一对一:人和身份证
  2.一对多:部门和员工
  3.多对多:用户角色

    一对多关系映射

        编写Department实体类及映射文件

        //1.实体类
        public class Department {

            private int id;
            private String name;
            private String address;
            getter/setter...
        }
        //2.映射文件

        <!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.snail.hibernate.entity">
            <class name="Department" table="departments">
                <id name="id" column="id" type="int">
                    <generator class="identity"/>
                </id>
                <property name="name" column="name" type="string"/>
                <property name="address" column="address" type="string"/>
            </class>
        </hibernate-mapping>

        编写Employee实体类和映射文件

        //1.实体类
        public class Employee {

            private int id;
            private String name;
            private int age;
            private String address;
            private float salary;
            private Date birthday;
            getter/setter...
        }
        //2.映射文件
        <!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.snail.hibernate.entity">
            <class name="Employee" table="employees">
                <id name="id" column="id" type="int">
                    <generator class="identity"/>
                </id>
                <property name="name" column="name" type="string"/>
                <property name="address" column="address" type="string"/>
                <property name="age" column="age" type="int"/>
                <property name="salary" column="salary" type="float"/>
                <property name="birthday" column="birthday" type="timestamp"/>

                <!-- 处理和部门的多对一关系 -->
                <many-to-one name="department" class="com.snail.hibernate.entity.Department" column="departmentId"/>
            </class>
        </hibernate-mapping>

        配置员工与部门的多对一关系

            实体类

            // 构建和部门的多对一关系
            private Department department;

            映射文件

            <!-- 处理和部门的多对一关系
                colum:表中外键的名称
                class:数据类型,如果在同包中可以省略不写
             -->
            <many-to-one name="department" class="com.snail.hibernate.entity.Department" column="departmentId"/>

        配置双边关系映射

            部门实体类

            //构建和员工的一对多关系
            private Set<Employee> employees;

            映射文件

            <!-- 配置集合类型的数据 -->
            <set name="employees">
                <!-- 查询条件,员工表的外键 -->
                <key column="departmentId"/>
                <!-- 配置一对多的关系 -->
                <one-to-many class="Employee"/>
            </set>

    一对一关系映射

        一对多的特殊情况

        <!-- 和人的一对一的关系 -->
        <many-to-one name="user" column="userId" unique="true"/>

        one-to-one进行处理

        <id name="id" column="id" type="int">
            <generator class="foreign">
                <!-- 数据来源于那个属性 -->
                <param name="property">user</param>
            </generator>
        </id>
        <property name="number" column="number" type="string"/>

        <one-to-one name="user" constrained="true"/>

    多对多关系映射

        many-to-many

        <!-- 多对多关系映射 
            table:中间表的表名
        -->
        <set name="roles" table="user_role_links" cascade="save-update">
            <!-- 生成的外键的名称 -->
            <key column="uId"/>
            <many-to-many column="rId" class="Role"/>
        </set>

懒加载

    数据库级联

    1.在保存数据的时候引用了数据库中不存在的数据
        object references an unsaved transient instance - save the transient instance before flushing: com.snail.hibernate.entity.Employee
         1.
            级联--->主表在执行相应的操作的时候,副表执行什么动作
            级联操作
            Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似的操作 java代码中设置了对象关联之后有效  两对象有关联的时候可以配置
                  none,all,save-update ,delete, lock,refresh,evict,replicate,persist,
                  merge,delete-orphan(one-to-many) 。一般对many-to-one,many-to-many不设置级联,在<one-to-one>和<one-to-many>中设置级联
            在one-to-many或many-to-many设置对象关联的时候配置inverse="true"(及对该属性的设置不理会)则表名one这边放弃对关系的维护交由many端维护
                  故在many端要set对象的关联
                  所谓的关系的维护就是更新外键或对中间表数据的增删 inverse不能在有序的集合中使用
          2. 先将数据保存到数据库   

    懒加载

    inserse为false的时候:
    -- 添加
    insert into departments (name, address) values (?, ?)
    -- 附表中条件数据
    insert into employees (name, address, age, salary, birthday, departmentId) values (?, ?, ?, ?, ?, ?)
    insert into employees (name, address, age, salary, birthday, departmentId) values (?, ?, ?, ?, ?, ?)

    -- 维系关系
    update employees set departmentId=? where id=?
    update employees set departmentId=? where id=?

    inserse为true的时候:
    -- 添加
    insert into departments (name, address) values (?, ?)
    -- 附表中条件数据
    insert into employees (name, address, age, salary, birthday, departmentId) values (?, ?, ?, ?, ?, ?)
    insert into employees (name, address, age, salary, birthday, departmentId) values (?, ?, ?, ?, ?, ?)

    -- 懒加载的问题:主表查询附表中的数据时候
    failed to lazily initialize a collection of role: com.snail.hibernate.entity.Department.employees, could not initialize proxy - no Session
      1.设置一个属性

        lazy修改false
      2.在session关闭之前手动访问附表中的数据--->触发第二次查询

        @Test
        public void getDepartment(){
            Department department = session.get(Department.class, 1);//--->附表中的数据是一个代理对象-->马上去查询数据库的数据
            //department.getEmployees().size();

            Hibernate.initialize(department.getEmployees());//初始化代理对象

            //关闭session
            session.close();
            System.out.println(department.getName());

            System.out.println(department.getEmployees().size());//员工表
        }

    get和load方法的区别
        get:马上去查询数据库的数据,附表中的数据是一个代理对象,集合属性(附表中的数据)为代理对象
        load:并不会马上查询数据库中的数据,返回的是代理对象

缓存

    一级缓存:session级别,

    @Test
    public void firstCache(){
        //session级别,默认开启
        Employee employee = session.get(Employee.class, 1);

        System.out.println("*******************");

        session.clear();//清除所有的缓存

        //第二次查询session中缓存中获取
        session.get(Employee.class, 1);
    }

    二级缓存,sessionfactory级别--->集成ehcache,默认没有开启的

        引入依赖

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>5.0.11.Final</version>
        </dependency>

        添加ehcache的配置文件

        修改hibernate的配置文件,开启二级缓存

        <!-- 开启二级缓存 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 允许查询缓存,默认值为true -->
        <property name="hibernate.cache.use_query_cache">true</property>
        <!-- 设置缓存的实现者 -->
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

        告诉hibernate哪些实体类需要进行二级缓存

        <!-- 配置哪些实体类需要进行缓存 -->
        <class-cache usage="read-only" class="com.snail.hibernate.entity.Employee"/>

        测试代码

        @Test
        public void secondCache(){
            String hql="from Employee";
            Query query = session.createQuery(hql);
            //开启查询缓存
            query.setCacheable(true);

            query.list();//-->将数据放入二级缓存中

            //将session关闭掉
            session.close();

            System.out.println("************");
            session=sessionFactory.openSession();
            query = session.createQuery(hql);
            //开启查询缓存
            query.setCacheable(true);

            query.list();
        }

spring

环境搭建

    添加依赖

    <!-- 变量 -->
    <properties>
        <spring.version>4.3.5.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
    </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
    </dependencies>

    编写配置文件beans.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:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans  
               http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  
               http://www.springframework.org/schema/context  
               http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    </beans>

    添加log4j的配置文件

    ### direct log messages to stdout ###
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

    log4j.rootLogger=debug, stdout

    # 定义一个包级别的日志
    logger.org.springframework=debug

    初始化spring容器

    @Test
    public void init(){
        ApplicationContext act=new ClassPathXmlApplicationContext("beans.xml");
    }

ioc:反转控制(应用本身不负责对象的创建及维护,交由第三方容器进行处理)

    bean的配置

    <!-- bean对象 
        id:bean的唯一标识,规定首字母小写
        name:bean的名称,可以重复
        class:bean的全限定名
        scope:bean的作用域,单例(默认的)、原型(每次都会创建一个新的实例)、(web环境)session、request、global session
        destroy-method:bean销毁时调用的方法
    -->
    <bean id="userAction" class="com.woniu.spring.action.UserAction">

    </bean>

    从spring容器中获取bean

        @Test
        public void getUserAction(){
    //      UserAction userAction=new UserAction();
            ApplicationContext act=new ClassPathXmlApplicationContext("beans.xml");
    //      UserAction userAction = act.getBean(UserAction.class);//通过类型获取实例
    //      userAction.save();
            UserAction userAction=(UserAction) act.getBean("userAction");
            userAction.save();
        }

    依赖注入

        注入其他bean

        <bean id="userAction" class="com.woniu.spring.action.UserAction">
            <!-- 注入数据 
                name:表示set的方法的名称
                ref:其他bean对象的id
                value:字面值
            -->
            <property name="userDao" ref="userDao"/>
        </bean>

        <bean id="userDao" class="com.woniu.spring.dao.UserDao"></bean>

        注入字面值

            简单数据类型

            <bean id="userDao" class="com.woniu.spring.dao.UserDao">
                <property name="name" value="扎实"/>
                <property name="age" value="30"/>
                <property name="salary" value="45.5"/>
            </bean>

            集合类型
                list
                set
                map
                properties

        采用注解+扫描的方式
            注解
                Controller注解:标识action,控制器
                Service注解:标识业务层
                Repository注解:标识dao层(持久层)
                Component注解:模棱两可注解,不好区别所在的层次

            扫描: 扫描指定的包或者指定的注解

            <!-- 指定需要扫描的包,包之间采用逗号风格 -->
            <context:component-scan base-package="com.woniu.spring.action,com.woniu.spring.dao"/> 

            注入
                Resource:按名称注入,如果没有指定name属性直接通过类型注入
                AutoWired:按类型注入,可以和@Qualifier注解联合使用来表示注入的bean对象

aop:面向切面编程,核心为动态代理
    术语
        切面:增强、连接点
        切点
        连接点
            增强方法中可以通过joinpoint来获取链接点的信息
            环绕增强必须通过ProceedingJoinPoint来获取连接点的信息
        增强----->面向方法级别
            前置
            后置
            环绕
            异常
            最终
        目标对象

    添加依赖

    <!-- 引入aop的依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>

    编写增强

    public class Logger {

        // 定义前置增强
        public void doAccessCheck() {
            System.out.println("前置增强");
        }

        // 定义后置增强
        public void doReturnCheck() {
            System.out.println("后置增强");
        }

        // 环绕增强
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("环绕增强");
            Object[] args = pjp.getArgs();//获取调用方法的参数列表
            Signature signature = pjp.getSignature();//方法的签名
            Object proceed = pjp.proceed();//调用目标对象的方法,返回调用方法之后的返回值
            return proceed;
        }

    }

    编写目标对象

    public class UserService {

        public void saveUser(String name) {
            System.out.println(this.getClass().getSimpleName() + "--->saveUser:" + name);
        }

        public void delteUser(String name) {
            System.out.println(this.getClass().getSimpleName() + "--->delteUser:" + name);
        }

        public void upateUser(String name) {
            System.out.println(this.getClass().getSimpleName() + "--->upateUser:" + name);
        }

    }

    配置aop

    <?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:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans  
               http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  
               http://www.springframework.org/schema/context  
               http://www.springframework.org/schema/context/spring-context-4.2.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

        <!-- 配置目标对象 -->
        <bean id="userService" class="com.woniu.spring.advice.UserService"/>

        <!-- 配置增强 -->
        <bean id="logger" class="com.woniu.spring.advice.Logger"/>

        <!-- 配置切面 -->
        <aop:config>
            <!-- 切点
                expression:切面表达式 execution(<修饰符类型>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?),带?表示可选
             -->
            <aop:pointcut expression="execution(* com.woniu.spring.advice.UserService.*(..))" id="p1"/>
            <!-- spring给我们定义的切面:只能同时定义一个切面 -->
            <!-- <aop:advisor advice-ref=""/> -->
            <!-- 切面 -->
            <aop:aspect ref="logger">
                <aop:before method="doAccessCheck" pointcut-ref="p1"/>
                <aop:around method="doBasicProfiling" pointcut-ref="p1"/>
                <aop:after method="doReturnCheck" pointcut-ref="p1"/>
            </aop:aspect>
        </aop:config>

    </beans>

spring jdbc
    spring 提供了一个模板类jdbctemplate--->Jdbc的包装

    依赖

    <!-- 引入spring jdbc的依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.5.RELEASE</version>
    </dependency>

    <!-- mysql的驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>

    <!-- c3p0的数据源 -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>

    配置文件

        配置数据源

        <!-- 配置数据源:配置jdbc的信息 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.user}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>

        配置jdbctemplate

        <!-- 模板类 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"/>
        </bean>

    基于xml的事务管理器

        使用tx命令空间

        <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:context="http://www.springframework.org/schema/context"
            xmlns:tx="http://www.springframework.org/schema/tx"
            xsi:schemaLocation="http://www.springframework.org/schema/beans  
                   http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  
                   http://www.springframework.org/schema/context  
                   http://www.springframework.org/schema/context/spring-context-4.2.xsd
                   http://www.springframework.org/schema/aop 
                   http://www.springframework.org/schema/aop/spring-aop-4.2.xsd  
                   http://www.springframework.org/schema/tx  
                   http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

        配置

        <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save" isolation="DEFAULT" propagation="REQUIRED"/> <tx:method name="update" isolation="DEFAULT" propagation="REQUIRED"/> <tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED"/>

            <tx:method name="get*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
            <tx:method name="load*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>

            <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED"/>
        </tx:attributes>    

        </tx:advice>

        <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution( ..Service.(..))"/> </aop:config>
    基于注解的事务管理器
    说明
        事务的传播行为
            REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。
            NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
            REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
            MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
            SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
            Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
            NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效
        事务的隔离级别数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read ,如Mysql
            Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
            Read Commited:读已提交数据(会出现不可重复读和幻读)
            Repeatable Read:可重复读(会出现幻读)
            Serializable:串行化
            说明
                脏读:一个事务读取到另一事务未提交的更新新据。
                不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
                幻读:一个事务读取到另一事务已提交的insert数据。

    基于注解的配置

    @Configuration
    @PropertySource("classpath:jdbc.properties")
    public class AnnotationSpring {

        @Value("${jdbc.url}")
        private String url;

        @Value("${jdbc.driver}")
        private String driver;

        @Value("${jdbc.user}")
        private String user;

        @Value("${jdbc.password}")
        private String password;

        @Bean
        public DataSource dataSource() throws Exception{
            ComboPooledDataSource dataSource =new ComboPooledDataSource();
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(user);
            dataSource.setPassword(password);
            return dataSource;
        }

        @Bean
        public JdbcTemplate jdbcTemplate(DataSource dataSource){
            JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);
            return jdbcTemplate;
        }

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值