JDBC笔记一

1. JDBC简单应用

public class FirstJDBC {
    public static void main(String[] args) throws Exception {
        // 0.准备变量
        String driver = "com.mysql.jdbc.Driver";// mysql驱动实现类
        String url = "jdbc:mysql://localhost:3306/day15_db";// 确定数据库服务器地址,端口号,使用数据库
        String user = "root";// 登录名称
        String password = "1234";// 登录密码
        // 1.注册驱动
        Class.forName(driver);
        // 2.获得链接
        Connection conn = DriverManager.getConnection(url, user, password);
        // 3.获得语句执行者
        Statement st = conn.createStatement();
        // 4.发送sql语句,查询  结果相当于一个set集合,每一个成员表示数据库表中一条记录
        ResultSet rs = st.executeQuery("select * from t_user ");
        // 5.处理结果
        rs.next();// 移动到第一行
        // getXxx获取某一行的指定列或字段值  getXxx(int 列数),getXxx(String 字段名)
        int id = rs.getInt("id");
        String username = rs.getString("username");
        String userPassword = rs.getString("password");
        System.out.printf("id:"+id+" username:"+username+" password:"+password);
        // 6.释放资源,优先关闭最后使用的
        rs.close();
        st.close();
        conn.close();
    }
}

2. Junit 测试

1.需要导入两个jar包
junit.jar
hamcrest-core.jar
放入libs目录下即可
2.类名alt+enter,然后选择需要添加测试的方法即可

测试用例方法,公共 没有返回值 非静态 方法名自定义 没有参数列表
方法名建议:test方法名()

public class DemoTest {
    private Demo demo;

    @Before  //测试方法执行前
    public void myBefore(){
        System.out.println("之前");
        demo = new Demo();
        //初始化数据
    }
    @After   //测试方法执行后
    public void myAfter(){
        System.out.println("之后");
        //方法资源
    }
    @Test(timeout=1000)  //timeout 设置测试时间,如果超时性能有问题
    public void testAdd() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
        }

        int sum = demo.add(1, 2);
        //断言
        Assert.assertEquals(3, sum);
        assertEquals(3, sum);  //使用静态导入的结果
    }
    @Test
    public void testMul() {
        int sum = demo.mul(1, 2);
    }
    @BeforeClass
    public static void myBeforeClass(){
        System.out.println("类之前");

    }
    @AfterClass
    public static void myAfterClass(){
        System.out.println("类之后");
    }
}

3. JDBC工具类

public class JdbcUtils {
    private static String url;
    private static String user;
    private static String password;

    // 这些配置文件的东西只用加载一次就可以了,写在静态代码块中
    static {
        try {
            // 参数配置应该放在配置文件中
            // 1. 加载properties文件
            // 方式1:使用ClassLoader加载资源
            //InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbcInfo.properties");
            // 方式2:使用Class对象加载,必须加上/,表示src
            InputStream is = JdbcUtils.class.getResourceAsStream("/jdbcInfo.properties");
            // 2. 解析配置文件
            Properties properties = new Properties();
            properties.load(is);
            // 3. 获得配置文件中的数据
            String driver = properties.getProperty("driver");
            url = properties.getProperty("url");
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            // 4. 注册驱动
            Class.forName(driver);
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 获得连接
     */
    public static Connection getConnection(){
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        } catch (Exception e){
            // 将编译时异常转换成运行时异常,开发中常见运行时异常
            // throw new RuntimeException(e);
            // 此处可以使用自定义异常
            // 类与类之间进行数据交换时可以使用return返回数据.
            // 也可以使用自定义异常返回值,调用者try{} catch(e){ e.getMessage() 获得需要的数据}
            throw new MyConnectionException(e);
        }
    }

    /**
     * 释放资源
     */
    public static void closeResource(Connection conn, Statement st, ResultSet rs){
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (Exception e){
            throw new RuntimeException(e);
        } finally {
            try {
                if (st != null) {
                    st.close();
                }
            } catch (Exception e){
                throw new RuntimeException(e);
            } finally {
                try {
                    if (conn != null) {
                        conn.close();
                    }
                } catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

4. 自定义异常

继承RuntimeException 覆写方法
将编译时异常转换成运行时异常,开发中常见运行时异常
throw new RuntimeException(e);
类与类之间进行数据交换时可以使用return返回数据.
也可以使用自定义异常返回值,调用者try{} catch(e){ e.getMessage() 获得需要的数据}

5. 模板代码

public void demo1(){
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;

    try {
        conn = JdbcUtils.getConnection();
        // ....
    } catch (Exception e){
        throw new RuntimeException(e);
    } finally {
        // 释放资源
        JdbcUtils.closeResource(conn,st,rs);
    }
}

6. Api详解

6.1 注册驱动

1.0 所有的驱动实现类必须实现规范接口,java.sql.Driver接口
1.1 原始编写方式:DriverManager.registerDriver(new Driver());
    特点:必须导包  import com.mysql.jdbc.Driver; 
        硬编程,内容写死的,无法扩展,不易于数据变迁(更换)
    源码:public class com.mysql.jdbc.Driver implements java.sql.Driver

1.2 建议编写方式:Class.forName("com.mysql.jdbc.Driver");
    特点:指定类如果有static{} 里面的内容将自动执行。
    源码:
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
        mysql Driver实现类,将自己进行注册。
    优点:使用字符串方法加载到内容(注册),之后可以将驱动配置到配置文件中,之后只需要修改配置文件,数据库就更改。
1.3 此行代码可省略,建议不要省略。
    mysql驱动 高版本,将自动加载驱动并注册。
        文件:mysql-connector-java-5.1.22-bin.jar/META-INF/services/java.sql.Driver
        内容:com.mysql.jdbc.Driver 
1.4 错误总结
    * ClassNotFoundException ,是否导入jar包?驱动名称是否正确?
    * java.lang.NoClassDefFoundError   如果配置文件放置位置错误就会报这个异常
1.5 mysql  com.mysql.jdbc.Driver 子类  org.gjt.mm.mysql.Driver
    源码:public class Driver extends com.mysql.jdbc.Driver

6.2 获得连接

2.1 介绍
    使用连接接口:java.sql.Connection,只有获得连接才可以操作数据库。
    通过DriverManager 驱动管理者类获得连接。getConnection(url,user,password)
2.2 url 确定访问数据库位置
    格式#   协议:子协议:子名称
        协议  固定值 -->  jdbc
        子协议  --> 确定数据,例如:mysql、oracle 等
        子名称  --> localhost主机,3306端口,day15_db数据库名称
        参数  --> jdbc:mysql://localhost:3306/day15_db?useUnicode=true&characterEncoding=UTF-8
            设置请求编码,安装如果指定UTF-8,不需要设置的。如果安装时设置的编码为ISO8859-1编码,中文乱码需要如上处理。
    主机默认localhost,端口默认3306
        简写方式 -->  jdbc:mysql:///day15_db
2.3 错误总结
    * Unknown database 'day15_db2' , 表示数据库不存在。
    * Access denied for user 'root'@'localhost' (using password: YES)  , 账号和密码不匹配

6.3 Connection提供的不同的操作对象

3.1 操作对象
        * Statement , 语句执行者,获得方式 conn.createStatement()【】
        * PreparedStatement , 预处理对象,获得方法  conn.prepareStatement(sql)【】
        * 必须先提供sql语句,进行预先处理。
        * CallableStatement 存储过程,  prepareCall(String sql),将sql语句编写数据库,相当于数据库函数,执行时    只需要传递实际参数
3.2 Statement
    int executeUpdate(sql) 执行DDL/DML语句,返回影响的行数。【】
    ResultSet  executeQuery(sql) 执行DQL语句,返回结果集(相当于一个表)【】
    boolean execute(sql) 
        返回值true,表示执行DQL语句,必须通过  getResultSet() 获得结果集
        返回值false,表示执行DML/DDL语句,必须通过  getUpdateCount() 获得影响行数 
3.3 滚动结果集 (了解)
    * jdbc规范 默认结果集forward ,只能向前的结果集。
    * 滚动结果集:可以向前,也可以向后。mysql 直接支持滚动
    * ResultSet api
        next() 下一个(向前)
        previous() 上一个(向后)
    * 结果集ResultSet要滚动,前提Statement必须设置支持的。 
    * 使用方法:createStatement(int resultSetType, int resultSetConcurrency)
        resultSetType - 结果集类型,
                ResultSet.TYPE_FORWARD_ONLY,仅仅只能向前
                ResultSet.TYPE_SCROLL_INSENSITIVE,可以滚动,不敏感。滚动中,数据库发生改变,结果集内容不改变的。
                    数据库的数据是否同步到结果集ResultSet中。
                ResultSet.TYPE_SCROLL_SENSITIVE,可以滚动,敏感。滚动中,数据库数据发生改变,结果集内容改变。
        resultSetConcurrency - 并发类型
            ResultSet.CONCUR_READ_ONLY ,结果集只能读,不能改。
            ResultSet.CONCUR_UPDATABLE,结果集可以更新,数据一并更改。
                结果集数据同步到数据库
    * 操作
        获得  getXxx(int)获得指定列号的内容,  getXxx(String)获得指定列名(字段)的内容
        例如:getString(4) 获得第4列,  getString("username")  获得字段名称username的值

7.SQL注入

sql注入 :用户输入实际参数,作为了sql语句语法的一部分,数据库编译在执行时生效的。
select * from t_user where username = ‘jack’ or 1=1 or 1=’’ and password = ‘12345’
select * from t_user where username = ‘jack’ –’ and password = ‘12345’
解决方案:
1. 手动方式:\转义单引号
2. 使用预处理对象,防止sql注入
1) 先提供sql语句,将实际参数使用占位符?替换。
例如:select * from t_user where username = ? and password = ?
2) 获得预处理对象,获得对象时必须提供sql语句,让sql语句预先进行编译。
3) 设置实际参数
PreparedStatement 提供 setXxx(int , Object)
参数1:int表示 ?位置,从1开始。
参数2:Object具体类型,如果字符串String等。
3. PreparedStatement 接口 是 Statement接口的子接口。
但是execute(sql) 父类方法,子类使用抛异常。使用的没有参数。

8.预处理对象PreparedStatement

特点:
    sql 易于编写,更佳清晰
    提高性能,编译一次,可以执行多次。
编写步骤:
    1.提供sql语句,并将实际参数使用?占位符
    2.获得预处理对象,注意提供sql语句
    3.设置实际参数,将?替换回来
    4.执行,注意:不能设置sql语句。
Statement  和 PreparedStatement  对比:
    一般情况使用PreparedStatement,之后学习框架DbUtils底层使用PreparedStatement,hibernate底层使用也是。
    如果使用Statement,必须保证sql都是自己编写,实际参数都是自己传递的。
PreparedStatement 应用场景
    1.防sql注入
    2.大数据
        大数据类型:blob 字节 、text 字符
    3.批处理

MySql笔记

1.数据库知识

分类:

  1. 网状型数据库
  2. 层次型数据库
  3. 关系型数据库
    • Oracle : 甲骨文,oracle公司,大型数据,收费
    • db2 : IBM,大型数据,收费
    • Sql server : 微软,中性数据,收费。(access office 小型)
    • Mysql : 免费。小型。(开源) –oracle

2.Mysql安装

  1. 设置数据库存放目录
    QQ截图20160630165126.png

  2. 选择配置类型 QQ截图20160630165743.png

  3. 选择服务类型 QQ截图20160630165834.png

  4. 选择数据库类型 QQ截图20160630165920.png

  5. 设置并发连接数 QQ截图20160630170001.png

  6. 配置网络参数 QQ截图20160630170103.png

  7. 设置字符集 QQ截图20160630170134.png

  8. 设置服务和环境静变量 QQ截图20160630170302.png

  9. 设置数据库密码 QQ截图20160630170328.png

  10. 测试

    C:\Users\xxxxx>mysql --version
    mysql  Ver 14.14 Distrib 5.5.27, for Win64 (x86)

3.Mysql登录&修改数据库密码

mysql -h 主机(ip地址) -u 账号 -p 密码

use mysql;
update user set password=password('1234') where user='root';

4.常用命令

  • 显示当前数据库服务器中的数据库列表 : show databases;
  • 使用数据库 : use 数据库名;
  • 显示数据库中的数据表 : show tables;
  • 显示当前所使用的数据库名称 : select database();
  • 显示当前数据库的状态 : status;
  • 显示某个表的表结构 : desc 表名;
  • 显示所有支持的字符集 : show character set;
  • 查看创建表的sql语句 : show create table 表名;

5.SQL语句

5.1 DDL数据定义语言(了解)

> 操作数据库或表结构

5.1.1 数据库操作

  • 创建数据库 : create database 数据库名称;
  • 查询数据库创建语句 : show create database 数据库名称;
  • 删除数据库 : drop database [if exists] 数据库名称;
  • 修改数据 : alter database 数据库名 character set 字符集 collate 比较方式不建议修改。

5.1.2 表结构

> 操作表之前,必须先切换数据库.
  • 创建表 : create table 表名(字段描述,字段描述,...);

    create table users(
        id varchar(32),
        name varchar(50),
        age int
    );
  • 删除表 : drop table 表名;

  • 修改字段类型 : alter table 表名 modify 字段名称 新类型;

  • 修改字段名称 : alter table 表名 change 旧字段 新字段 新字段类型;

  • 添加字段 : alter table 表名 add column 字段名称 字段类型;

  • 删除字段 : alter table 表名 drop column 字段名称;

  • 重命名表名 : alter table 表名 rename [to] 新表明;

5.1.3 数据类型

字符串

char(n) 固定字符串,例如:char(5) 表示可以存放5个字符,且必须是5个。
    如果插入 “abc”,结果“abc  ”  右边自动添加空格。
varchar(n) 可变长字符串,例如:varchar(5),表示最多存放5个字符,如果不够就原样存放。
    如果插入“abc”,结果“abc”

数字

bit            比特
tinyint        byte
mediumint    short
int            int        【】
bigint        long
float            float
double(m,d)    double 【】 --m数字长度,d精度及小数位
numeric        Number    所有数字
    例如:double(5,2) 5表示整个数字为5位,2表示小数位2个。最大值。999.99

时间日期

** 之后使用java日期时间类型:java.util.Date 。如果要使用java.sql..类型,只能存放dao层
date 日期                java.sql.Date
datetime 日期时间            ---
time 时间                java.sql.Time
timestamp 时间戳            java.sql.Timestamp

    sql转util : java.util.Date date = new java.sql.Date(long);
    util转sql : new java.sql.Date(  new java.util.Date().getTime()  )

大数据

字节:存放二进制  (java.sql.Blob :Binary Large Object 二进制大对象)
    TINYBLOB  255
    blob        64k
    longblob    4G
字符:存放文本 (java.sql.Clob :Character Large Object 字符大对象)
    TINYTEXT    255
    text        64k
    longtext 4G

5.2 DML数据操作语言(掌握)

> 对表中的数据进行增删改的操作

5.2.1 插入数据

insert into 表名(字段列表) values(字段对应值);

注意:

多个字段之间使用逗号分隔
字段值必须使用引号(建议单引号),如果是整形数据引号可以省略
字段默认值为null

5.2.2 更新数据

update 表名 set 字段名=字段值,字段名=字段值,...;
update 表名 set 字段名=字段值,字段名=字段值,...where 条件;

5.2.3 删除数据

delete from 表名 [where 条件];

注意:

delete from users; 删除表中的所有数据.进行删除操作留心,一般情况应用系统不进行数据删除,提供一个标记字段,逻辑删除(0表示已删除,1表示没有删除).

5.3 约束

给字段添加规则,约定内容编写。最终作用保证数据的完整性,一致性等。

5.3.1 主键约束

关键字:primary key
要求:数据唯一,不能为null.

1.定义表:声明字段时,定义主键.(primary key)只能修饰一个字段.
create table pk01(
id varchar(32) primary key ,
content varchar(50)
);
2.定义表,声明字段之后,在约束区域定义主键。—特点 constraint primary key (字段1,字段2,….) 可以设置多个字段
create table pk02(
id varchar(32),
content varchar(50),
constraint primary key (id)
);
3.定义表,声明字段,表创建之后。修改表结构添加约束。–特点:也可以设置多个字段,更灵活。
create table pk03(
id varchar(32),
content varchar(50)
);
alter table pk03 add constraint primary key (id);

5.3.2 唯一约束

关键字:unique
要求:被修饰的字段不能重复

1.定义表,声明字段时,定义唯一约束。— 特点:unique只能修饰一个字段
create table un01(
id varchar(32),
content varchar(50) unique
);
2.定义表,声明字段之后,在约束区域定义唯一约束。—特点 constraintunique (字段1,字段2,….) 可以设置多个字段
create table un02(
id varchar(32),
content varchar(50),
constraint unique (content)
);
3.定义表,声明字段,表创建之后。修改表结构添加唯一约束。–特点:也可以设置多个字段,更灵活。
create table un03(
id varchar(32),
content varchar(50)
);
alter table un03 add constraint unique (content);

5.3.3 非空约束

关键字:not null
要求:被修饰的字段不能为null

1.定义表,声明字段时,添加约束。
create table nn01(
id varchar(32),
content varchar(50) not null
);
create table nn02(
id varchar(32),
content varchar(50) not null default ‘dzd’
);

总结: 主键 = 唯一 + 非空

5.3.4 自动增长(mysql特有)

定义:
关键字:auto_increment
mysql特有的一个特殊的关键字,被修饰的字段将自动的累加(oracle没有,但提供徐磊sequence)
注意:
1.字段类型必须是整型,一般使用int
2.必须是key(主键/唯一),一般使用主键primary key
3.被auto_increment修饰的字段,不需要手动维护数据,mysql将自动维护
代码示例:
create table ai01(
id varchar(32) auto_increment, # 错误代码 不能用于字符串
content varchar(50)
);
create table ai02(
id int auto_increment, # 错误代码 必须是primary key
content varchar(50)
);
create table ai03(
id int primary key auto_increment, # 正确代码
content varchar(50)
);
面试题:
drop table ai03; #删除表,数据和表都不存在了。
delete from ai03; #删除所有数据,表仍然存在。表中计数器没有重置。
truncate table ai03; #清空所有数据,表中的计数器将重置归0。
delete 和 truncate 对比
delete 将数据删除了
truncate 先删除了表,再创建表。

5.3.5 外键约束

关键字:foreign key

5.3.6 删除约束

删除主键:mysql>   `alter table 表名 drop primary key;`
删除唯一:修改列
删除外键:mysql>   `alter table 表名 drop foreign key 名称;`

5.3.7 cmd命令行中文乱码处理

set names gbk;

5.4 DQL数据查询语言(掌握)

5.4.1 无条件查询

查询所有:

select * from users;
select id,name,age from user;

查询部分字段:

select id,name from user;

合并查询条件:

select id,concat(firstname,secondname),age from users;

字段别名:

select id,concat(firstname,secondname) as `姓名`,age from users;

特殊字符或者关键字使用重音符 ``

5.4.2 带条件查询

查询分数等于60的:

select * from users where count='60';

查询年龄大于18的学生:

select * from users where age > 18;

查询分数在60-80之间:

select * from users where count >= 60 and count <= 80;
select * from users where count between 60 and 80;

查询年龄是18或20的学生:

select * from users where age = 18 or age = 20;

模糊查询,不完全匹配:

字段:like
符号:    
    %匹配多个数据
        '云' 只能匹配一个云
        '%云' 匹配以云结尾的
        '云%' 匹配以云开头的
        '%云%' 包含云
    _匹配一个数据

查询分数等于60 或者 分数大于90并且年龄大于23

select * from users where count = 60 or count > 90 and age > 23;
select * from users where count = 60 or (count > 90 and age > 23);
### 运算符优先级  and 优先 or

查询没有考试的学生

select * from users where count is null;

查询所有考试的学生

select * from users where count is not null;

运算符 不相等 != <>

5.4.3 聚合函数

聚合函数:对表中的数据进行统计,显示一个数据(一行一列的数据)

聚合函数不统计 null值。

有多少条记录 count(* | 字段)

select count(*) from users;
select count(id) from users;    #7
select count(count) from users;    #6

平均成绩 avg

select avg(count) from users;    #不精准(没有null)
select sum(count)/count(id) from users; #精准(计算null)

最高成绩 max

select max(count) from users;

最小年龄 min

select min(age) from users;

班级总成绩 sum

select sum(count) from users;

查询所有的年龄数(排序)

去重复 distinct
排序 select..... order by 字段1 关键字, 字段2 关键字,....
### 关键字 asc 升序 , desc 降序
select distinct age from users order by age desc;   # age asc 等效 age [asc] 

5.4.4 分组查询

添加班级字段(classes)

alter table users add column classes varchar(3);
update users set classes = 1;
update users set classes = 2 where id='u005' or id ='u006' or id ='u007';
update users set classes = 2 where id in ('u005','u006','u007');

查询1班和2班的平均成绩

分组  select ... group by 分组字段;
select classes,avg(count) from users group by classes;
select classes,sum(count)/count(id) from users group by classes;

查询班级,平均成绩不及格的,班级成员信息

(查询2班,成员信息)
select * from users where classes = 2;

多表操作

select * from A,B where A.classes = B.classes and avg < 60;

表的别名

select ... from 表名 [as] 别名

子查询,一条select语句,作为另一个select一部分。

select * from users,(
select classes,sum(count)/count(id) as cavg from users group by classes) as B
where users.classes = B.classes and cavg < 60;

子查询特点

#查询结果一行一列,可以使用
    select id,(xxx) from
    select ... from ... where id= (xxxx)
#查询结果一行多列(查询多个值),使用关键字 in ,all 等
#查询结果多行多列,可以当另一个表使用

Android Studio导出并使用aar和jar

今天想往项目中导入Zxing,不知道怎么就想到不使用library的方式而使用包的方式来导入,然后就有了下面的东西了.

AAR

AAR是Android Library的一种新的二进制分发格式,它把资源也一起打包,这样一来图片和布局资源文件也能够被同时分发。同时AAR还可以包含jar包.

1.生成AAR

当我们运行工程后,该工程的/build/outputs/arr下包含Android Studio自动打包的AAR文件

QQ截图20160627170457.png

2.使用AAR

将AAR文件拷贝到项目的libs目录下,然后在项目的build.gradle文件中配置即可

apply plugin: 'com.android.application'
android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "com.qtparking.btool_as"
        minSdkVersion 11
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

// 添加arr文件的引用  还要在dependencies里面添加引用
repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'
    compile 'com.android.support:design:23.3.0'
    // 添加AAR文件的引用,文件名字为zxing,类型为aar
    compile(name:'zxing', ext:'aar')
}

Jar

Jar包应该不陌生了,但是如何使用Android Studio导出jar包呢?

1.配置gradle任务

在需要打包成jar包的项目的build.gradle添加gradle任务:

apply plugin: 'com.android.library'
android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 21
        testApplicationId "com.android.volley.tests"
        testInstrumentationRunner "android.test.InstrumentationTestRunner"
    }

    lintOptions {
        abortOnError false
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }
}

task makeJar(type: Copy) {
    delete 'build/libs/lfVolley.jar'    //删除旧的jar包
    from('build/intermediates/bundles/release/')    // 文件来自哪里
    into('build/libs/')        // 生成的jar包存放目录
    include('classes.jar')
    rename ('classes.jar', 'lfVolley.jar')    // 重命名成我们的jar包
}
makeJar.dependsOn(build)

dependencies {
    compile 'com.android.support:support-v4:21.0.3'
}

2.执行gradle任务

有的文章中推荐我们执行:./gradview makeJar.但是,我个人测试是无法成功的.
可以通过Android Studio来执行gradle任务:

2.1在Gradle projects中寻找需要打包的Module:

QQ截图20160627171318.png

2.2找到makeJar的gradle任务并双击执行:

QQ截图20160627171331.png

2.3gradle任务执行完成后显示如下:

QQ截图20160627171348.png

至此,就完成了jar包的生成,可以直接拷贝到其他项目中使用了.

TODO:
1.有一个小想法,这个是不是和插件化有点关系呢,从来没有了解过插件化,还不知道怎么弄呢.后面有时间再说吧.
2.后面研究下如何导出混淆的aar和jar,现在没有时间研究呢还.

MVC介绍&用户管理系统

MVC

MVC基本定义

一种软件设计模式,B/S架构都支持。例如:java、.net、php等
思想:业务逻辑处理与数据显示相分离。

Model:模型,用于封装数据
View:视图,用于显示数据
Controller:控制器,用于控制正常执行。

mvc.png

Web项目分包结构

QQ截图20160624175200.png

面向接口编程

QQ截图20160624175227.png

用户管理系统

功能分析

  • 注册
  • 登录
  • 查询所有用户
  • 查询详情
  • 修改用户
  • 删除用户

技术分析

  • MVC三层架构
  • xml/dom4j
  • servlet/jsp/javabean

导入需要的jar包

解析xml需要

QQ截图20160624173426.png

包结构

com.lujiahao.web.servlet    web层
com.lujiahao.service        service层
com.lujiahao.dao            dao层
com.lujiahao.domain            javabean
com.lujiahao.utils            工具包

数据库

暂定使用xml

xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="u001">
        <username>jack</username>
        <password>1234</password>
        <gender>男</gender>
        <age>18</age>
    </user>
</users>

JavaBean

数据库定义完成之后就开始编写JavaBean

dao层实现

随便写点数据的增删改查功能

数据校验

表单校验Bean - UserFormBean

一般都是写在和servlet同级的包里面
这种类型的bean里面所有的字段都是字符串
用于获得浏览器发送的数据,并对数据的有效性进行校验

  1. 提供校验validate()
  2. 记录每一项的校验结果

具体代码实现:

public class UserFormBean {
    private String id;
    private String username;
    private String password;
    private String repassword;
    private String gender;
    private String age;// 因为服务器传过来的数据都是string类型的

    public UserFormBean() {}

    public UserFormBean(String id, String username, String password, String repassword, String gender, String age) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.repassword = repassword;
        this.gender = gender;
        this.age = age;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getRepassword() {
        return repassword;
    }
    public void setRepassword(String repassword) {
        this.repassword = repassword;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }

    // 记录错误信息 key:对应字段  value:提示信息
    private Map<String,String> errorMsg = new HashMap<>();
    /**
     * 校验方法
     */
    public boolean validate() {
        boolean temp = true;
        // 用户名不能为空
        if (username == null || "".equals(username)) {
            errorMsg.put("usernameMsg","用户名不能为空");
            temp = false;
        }
        if (password == null || "".equals(password)) {
            errorMsg.put("passwordMsg","密码不能为空");
            temp = false;
        } else if (! password.equals(repassword)){
            errorMsg.put("repasswordMsg","确认密码和密码不一致");
            temp = false;
        }
        return temp;
    }
    public Map<String, String> getErrorMsg() {
        return errorMsg;
    }
}

使用接口

三层结构每一层都应该是有接口和具体的实现类

QQ截图20160629180224.png

使用Intellj重构代码

QQ截图20160629180029.png

BeanUtils

通过封装的类来简化参数的自动封装
使用了反射和内省

初始代码:

// 1.获取请求参数
String id = request.getParameter("id");
String username = request.getParameter("username");
String password = request.getParameter("password");
String repassword = request.getParameter("repassword");
String gender = request.getParameter("gender");
String age = request.getParameter("age");

/**
 * 数据校验
 */
UserFormBean userFormBean = new UserFormBean(id,username,password,repassword,gender,age);

封装后的代码:

UserFormBean userFormBean = MyBeanUtils.populate(UserFormBean.class,request.getParameterMap());

BeanUtils详细代码:

public class MyBeanUtils {

    /**
     * 创建JavaBean实例,并自动将对应的参数进行封装
     * @param beanClass
     * @param parameterMap
     * @param <T>
     * @return
     */
    public static <T> T populate(Class<T> beanClass, Map<String,String[]> parameterMap){
        try {
            // 1.使用反射创建javabean实例
            T bean = beanClass.newInstance();
            // 2.获得javabean属性(property username-->setUsername()-->执行set方法,数据来自map
            // 2.1获得所有属性--使用内省(java.beans.Introspector):jdk提供工具类,用于操作javabean
            // BeanInfo jdk提供用于对javabean进行描述(封装)对象
            BeanInfo beanInfo = Introspector.getBeanInfo(beanClass, Object.class);
            // 2.2 获得所有的属性描述对象
            PropertyDescriptor[] allPd = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor pd : allPd) {
                // 2.3 获得属性名称
                String propName = pd.getName();
                // 2.4 获得表单中对应的数据
                String[] allValue = parameterMap.get(propName);
                if (allValue == null) {
                    continue;// 当没有值的时候就跳过这个字段
                }
                String propValue = allValue[0];
                // 2.5 如果有值,将执行set方法
                if (propValue != null && !"".equals(propValue)) {
                    Method writeMethod = pd.getWriteMethod();// 相当于set方法    getReadMethod--相当于get方法
                    if (writeMethod != null) {
                        writeMethod.invoke(bean,propValue);
                    }
                }
            }
            return bean;
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

Volley学习第三篇-网络线程

前文概要

其实NetworkDispatcherCacheDispatcher是很相像的.感觉看NetworkDispatcher要比CacheDispatcher要简单.

因为它不需要进行判断缓存的过期时间和新鲜时间,仅仅是执行请求,然后在缓存响应结果,所以要简单些.

流程分析

同样的继承Thread,只看run()方法里面的就好了

同样的死循环,队列无内容时阻塞

  • 1.首先从队列中取出Request请求

    request = mQueue.take();
  • 2.判断请求是否取消

    if (request.isCanceled()) {
        request.finish("network-discard-cancelled");
        continue;
    }
  • 3.请求未取消,执行网络请求并得到NetworkResponse对象

    NetworkResponse networkResponse = mNetwork.performRequest(request);

    具体的网络请求还是需要看Network接口的实现类中是如何操作的.

  • 4.判断服务器返回码是否是304(资源未更新)

    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
        request.finish("not-modified");
        continue;
    }
  • 5.是否缓存请求的响应

    if (request.shouldCache() && response.cacheEntry != null) {
        mCache.put(request.getCacheKey(), response.cacheEntry);
        request.addMarker("network-cache-written");
    }
  • 6.分发

    mDelivery.postResponse(request, response);

小结

其实看明白先前的缓存线程之后,理解网络线程也不是难事了.

明天详细解释下具体响应的转化和分发流程.

Volley学习第二篇-缓存流程

前文概要

上篇说道Volley初始化的时候需要创建一个RequestQueue消息队列,下面就来看看这个RequestQueue.

RequestQueue

是一个队列管理器,里面维护了两个队列—CacheQueueNetowrkQueue.

Volley类里面的newRequestQueue方法中调用了队列的start()方法,就从这个方法入手.

public void start() {
    stop();  // Make sure any currently running dispatchers are stopped.
    // Create the cache dispatcher and start it.
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    mCacheDispatcher.start();

    // 这里默认创建4个线程
    // Create network dispatchers (and corresponding threads) up to the pool size.
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start();
    }
}

这里创建了1个CacheDispatcher和4个NetworkDispatcher.CacheDispatcher和4个NetworkDispatcher都是继承了Thread的两个线程,一个是缓存线程,另一个是网络线程.

其中DEFAULT_NETWORK_THREAD_POOL_SIZE中定义了网络线程的个数,可以根据不同的cpu核数来自定义开多少个网络线程(线程数 = cpu核数 * 2 + 1).

CacheDispatcher缓存线程的流程

它继承了Thread,所以只需要看它的run()方法就好了.

while (true) {
    try {
        // Get a request from the cache triage queue, blocking until
        // at least one is available.从缓存队列中不停的取出request,直到队列中只有一个请求的时候阻塞
        final Request request = mCacheQueue.take();
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            continue;// 请求取消就不读缓存了
        }

        // Attempt to retrieve this item from cache. 从缓存中获取缓存信息的实体
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.
            mNetworkQueue.put(request);// 缓存木有,将请求添加到网络请求队列
            continue;
        }

        // If it is completely expired 过期, just send it to the network.
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            mNetworkQueue.put(request);// 缓存过期,添加到网络请求队列
            continue;
        }

        // We have a cache hit; parse its data for delivery back to the request.
        request.addMarker("cache-hit");
        // 解析网络数据  这个是由请求对象request来解析的
        // 文档中说道:request对象负责请求和解析网络请求
        Response<?> response = request.parseNetworkResponse(
                new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");

        if (!entry.refreshNeeded()) {// 缓存是否需要刷新
            // Completely unexpired cache hit. Just deliver the response.
            mDelivery.postResponse(request, response);// 无需刷新,直接分发
        } else {
            // Soft-expired cache hit. We can deliver the cached response,
            // but we need to also send the request to the network for
            // refreshing.
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);// 更新缓存

            // Mark the response as intermediate.// 中间  媒介
            response.intermediate = true;

            // Post the intermediate response back to the user and have
            // the delivery then forward the request along to the network.
            mDelivery.postResponse(request, response, new Runnable() {
                @Override
                public void run() {
                    try {
                        mNetworkQueue.put(request);
                    } catch (InterruptedException e) {
                        // Not much we can do about this.
                    }
                }
            });
        }

    } catch (InterruptedException e) {
        // We may have been interrupted because it was time to quit.
        if (mQuit) {
            return;
        }
        continue;
    }
}

缓存的主体流程就在这个死循环里面,Volley的dispatcher的原理和Handler里面的looper的原理非常相似.

读取缓存分发到主线程

Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");

if (!entry.refreshNeeded()) {
    // Completely unexpired cache hit. Just deliver the response.
    mDelivery.postResponse(request, response);
} else {
    // Soft-expired cache hit. We can deliver the cached response,
    // but we need to also send the request to the network for
    // refreshing.
    request.addMarker("cache-hit-refresh-needed");
    request.setCacheEntry(entry);

    // Mark the response as intermediate.// 中间  媒介
    response.intermediate = true;

    // Post the intermediate response back to the user and have
    // the delivery then forward the request along to the network.
    mDelivery.postResponse(request, response, new Runnable() {
        @Override
        public void run() {
            try {
                mNetworkQueue.put(request);
            } catch (InterruptedException e) {
                // Not much we can do about this.
            }
        }
    });
}

详解

request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));`

entry.data是缓存的原始的byte数组,将byte数组响应头封装成一个NetworkResponse对象(Volley里面的网络响应的统一对象).
parseNetworkResponse()方法将NetworkResponse对象解析成Response供各种泛型的转换.

mDelivery.postResponse(request, response);

mDelivery对象时在CacheDispatcher的构造方法的时候赋值的,往上找找RequestQueue中的start()方法中mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);

继续找

public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery) {
    mCache = cache;
    mNetwork = network;
    mDispatchers = new NetworkDispatcher[threadPoolSize];
    mDelivery = delivery;
}

还找

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
    this(cache, network, threadPoolSize,
            new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

看mDelivery就是new ExecutorDelivery(new Handler(Looper.getMainLooper()))

ExecutorDelivery中构造方法中有一个非常重要的一行

public ExecutorDelivery(final Handler handler) {
    // Make an Executor that just wraps the handler.
    mResponsePoster = new Executor() {
        @Override
        public void execute(Runnable command) {
            handler.post(command);
        }
    };
}

handlerrunnable对象post出去,而handler是通过Looper.getMainLooper创建的,这样就是通过我们用主线程创建的Handler将响应发送到主线程中了.

至此,Volley缓存的读取/分发就完成了.

总结


官方图解中的绿色的那部分—缓存线程的流程就结束了.

后面我会慢慢肥西网络线程相关的东西.

Volley学习第一篇-框架入口.md

前文概要

这是我的Volley学习第一篇的笔记.
看了一个星期的各类教程之后的总结,准备把Volley的整体流程都总结一遍,同时会对Volley进行一些扩展,最后会跟着大神的脚步模仿一个Volley出来.

水平有限,望多指教.

资源

创建全局的RequestQueue

使用Volley的时候会创建一个全局的RequestQueue,一般会在自定义的Application类里面创建.

RequestQueue requestQueue = Volley.newRequestQueue(appContext);

Volley框架的入口点

这里我们就发现原来Volley的入口是Volley类里面的一个静态方法newRequestQueue()

public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}

Volley框架的真正的入口

这是一个参数的方法,会调用两个参数的newRequestQueue方法,传入一个默认的null.

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {

    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }

    Network network = new BasicNetwork(stack);

    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

    return queue;
}

这里才是真正的创建RequestQueue的地方,因为传入的第二个参数是null,所以stack== null是成立的,这是就会根据不同的系统版本创建不同的HttpStack子类的实例对象,然后将它包装成BasicNetwork对象.

同时第一行的时候就创建了Volley的缓存目录(当然你可以根据你的需求随意更改缓存目录的位置),并将缓存目录包装成一个DiskBasedCache对象.

至此,RequestQueue需要的缓存和网络请求对象就创建成功了,然后我们的RequestQueue请求队列就创建成功了.

通过start()方法就启动了我们的请求队列.

小结

两个newRequestQueue方法使用的这种方式其实非常巧妙,以前我看到这种方式就叫他重载调用(不知道叫的对不对,哈哈).

这是我的Volley系列的第一篇,好的开始,加油↖(^ω^)↗!

Android网络层二次封装

项目介绍

对Volley进行二次封装,方便使用和扩展。主要是学习封装的思想。

该示例主体代码来自传智的某位Android讲师,具体不清楚。

网络层封装示意图

网络层二次封装示意图.png
网络层二次封装具体流程.png


接下来前期准备

public class App extends Application {
    public static Context application;
    public static HttpLoader HL;

    @Override
    public void onCreate() {
        super.onCreate();
        application = this;

        // 初始化网络请求相关的核心类
        HL = HttpLoader.getInstance(this);
    }
}

不用我介绍了吧,记得在AndroidManiFest文件中添加name属性&&&&&&&联网权限

1.Activity实现HttpListener接口

HttpListener是我们连接网络层和UI层的桥梁,记得重写接口中的成功和失败的方法。

2.创建Protocol类发起网络请求

new MainProtocol(MainActivity.this,"15613566958").doRequest(this);

这样我们就发起了一个网络请求,感觉简单了好多。

具体MainProtocol的具体实现

public class MainProtocol extends BaseProtrocol {
    private String phone;
    private Context actContext;
    // 传入必备参数
    public MainProtocol(Context actContext,String phone) {
        this.actContext = actContext;
        this.phone = phone;
    }

    @Override
    public void doRequest(HttpLoader loader, HttpLoader.HttpListener listener) {
        HttpParams params = new HttpParams().put("phone", phone);
        // 发起GET请求
        loader.get(actContext, AppConstants.URL_COUPONS, params, AppConstants.REQUEST_CODE_COUPONS, listener);
    }
}

3.HttpLoader–网络请求类

单例类,不要问我为什么,这是网络请求的类啊,能不单利吗!!!!

其中mRequestQueue是保存请求队列的,mInFlightRequests是用来保存已经等待的请求,同时具有过滤重复请求的功能。

get方法会调用request方法,这里才是请求的主体,request方法在发起请求前会通过tryLoadCacheResponse方法首先读取缓存,然后在进行访问网络的操作。然后根据返回结果的不同,分别调用HttpListener的不同的回调方法,这样就把服务器的结果返回到UI层了。

/**
 * ResponseListener,封装了Volley的错误和成功的回调监听,并执行一些默认处理,同时会将事件通过HttpListener分发到UI层
 */
private class ResponseListener implements Response.ErrorListener, Response.Listener<String> {
    private HttpListener listener;
    private int requestCode;

    public ResponseListener(int requestCode, HttpListener listener) {
        this.listener = listener;
        this.requestCode = requestCode;
    }

    @Override
    public void onErrorResponse(VolleyError volleyError) {
        LLog.w("Request error from network!");
        volleyError.printStackTrace();
        mInFlightRequests.remove(requestCode);// 请求错误,从请求集合中删除该请求
        if (listener != null) {
            listener.onGetResponseError(requestCode, volleyError);
        }
    }

    @Override
    public void onResponse(String response) {
        mInFlightRequests.remove(requestCode);// 请求成功,从请求集合中删除该请求
        if (response != null) {
            //SystemClock.sleep(2000);
            LLog.i("Request success from network!");
            try {
                JSONObject jsonObject = new JSONObject(response);// 处理分发数据---解析json
                String status = jsonObject.getString("status");
                if (listener != null && response != null) {// 分发数据
                    if (("success".equals(status) || "ok".equals(status))) {
                        String data = null;
                        try {
                            data = jsonObject.getString("data");
                            listener.onGetResponseSuccess(requestCode, data);
                        } catch (Exception e) {
                            // 不是标准格式的时候就把原始数据传回去
                            listener.onGetResponseSuccess(requestCode, response);
                        }
                    } else if (("fail".equals(status) || "error".equals(status))) {
                        String errMsg = null;
                        try {
                            errMsg = jsonObject.getString("msg");
                        } catch (Exception e) {
                            errMsg = jsonObject.getString("message");
                        } finally {
                            listener.onGetResponseError(requestCode, new VolleyError(errMsg));
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                listener.onGetResponseError(requestCode, new VolleyError("解析问题"));
            }
        }
    }
}

这之中ResponseListener是关键,他关联了三方框架的回调方法和HttpListener的回调方法。然后不要问我为嘛onResponse里面写了这么多,你是为了健壮性更好吗?

我能骂街吗?
后台返回数据是乱七八糟的好不?

小结

好了,到了这里你肯定还没有看懂,废话,我才写了几篇文章,哪有那么快就写的让你看懂。
如果你还没有看懂的话就看我的代码去吧,我不是链接

还是看不懂?

找我要视频啊~

看完了再看这几篇文章就好了 多搞搞就好了

微信支付

写在前面的话

小豪从来都是一个认认真真写总结的人(●’◡’●)

微信支付

  1. APP端开发步骤说明
  2. Android接入指南
  3. demo下载

第一个坑

好像修改了应用签名的时候需要等着它审核。

Android集成微信支付正确姿势

  1. 引入微信的jar包

  2. 初始化注册到微信

    IWXAPI msgApi = WXAPIFactory.createWXAPI(this,Constants.APP_ID, true);// 第二个参数是是否校验签名,坑!!!
    msgApi.registerApp(Constants.APP_ID);        
    官方的什么鬼,不要理他,第三个参数记得要填true!
  3. 解析后台传入的订单信息

    就是逐个解析,但是,为什么不用Gson呢?
    {
         "appId": "你的APPID",
         "money": 4,
         "nonceStr": "d33JjYv6I8a8DmPG",
         "partnerId": "1264518301",
         "paySign": "1ADA353C0A710E144B77804F3B1A525F",
         "prepay_id": "wx2016041117561365055d7bd30985662433",
         "timeStamp": "1460368573",
         "wxPackage": "Sign=WXPay"
    }
  4. 将订单信息传入请求,调起微信支付

    PayReq request = new PayReq();
    request.appId = wx.getAppId();
    request.partnerId = wx.getPartnerId();
    request.prepayId = wx.getPrepay_id();
    request.nonceStr = wx.getNonceStr();
    request.timeStamp = wx.getTimeStamp();
    request.packageValue = wx.getWxPackage();
    request.sign = wx.getPaySign();
    //request.extData = "app data"; // optional
    msgApi.sendReq(request);// 调起微信支付

    此时,你以为就可以成功调起微信支付了。

    zhuangbi.info.jpg

  5. 支付结果的回调

    想要成功回调,还需要编写WXPayEntryActivity类(包名或类名不一致会造成无法回调)

    必须放在wxapi包下,且这个包必须是在根目录下 不能放在其他包下

    应该是这样的结构:

    QQ截图20160412143624.png

    清单文件应该是这样的:

    <activity
        android:name=".wxapi.WXPayEntryActivity"
        android:exported="true"
        android:launchMode="singleTop" />

    必须这样写!!!回调的Activity必须是:你的包名(微信demo里是:net.sourceforge.simcpux)+.wxapi.WXPayEntryActivity.java
    你就不!好,那你就别想回调成功。

    我仿佛看到了这样的嘴脸

    这一切文档里没有,demo里写错的!


需要注意,如果errorCode总是为 -1

请尝试通过下面方法解决:

  1. 用提交的签名的keystore文件打包
  2. 清理微信的缓存
  3. 看上面再来一遍

其他人的总结

  1. Android常用第三方支付
  2. 移动应用微信支付集成小结
  3. http://www.th7.cn/Program/Android/201501/351050.shtml
  4. http://www.tqcto.com/article/mobile/57931.html

最后的提醒

看完这篇如果还没有成功,那你就去骂腾讯吧