1 使用dbutils进行一对多、多对多的开发

1.1 准备

  • mysql驱动的pom.xml
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.42</version>
</dependency>
  • dbutils的pom.xml
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.7</version>
</dependency>
  • c3p0的pom.xml
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency> 
  • c3p0的配置文件c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///test</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </default-config> 

</c3p0-config>
  • c3p0Util.java
package com.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class C3P0Util {
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    public static DataSource getDataSource(){
        return dataSource;
    }

    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}
  • junit的pom.xml
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2 一对多开发

  • 用户和订单的SQL
/*SQLyog Ultimate v12.4.1 (64 bit)MySQL - 5.5.28 : Database - test**********************************************************************/

/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `customer` */

DROP TABLE IF EXISTS `customer`;

CREATE TABLE `customer` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `name` varchar(200) DEFAULT NULL,  `city` varchar(200) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `customer` */

/*Table structure for table `order` */

DROP TABLE IF EXISTS `order`;

CREATE TABLE `order` (  `id` int(11) NOT NULL AUTO_INCREMENT,  `num` varchar(200) DEFAULT NULL,  `price` double DEFAULT NULL,  `customer_id` int(11) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `fk_customer_id` (`customer_id`),  CONSTRAINT `fk_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `order` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
  • domain

    • Customer.java  
package com.domain;

import java.util.ArrayList;
import java.util.List;

/**
 * 客户
 */
public class Customer {
    private Integer id;
    private String name;
    private String city;

    private List<Order> orders = new ArrayList<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public List<Order> getOrders() {
        return orders;
    }

    public void setOrders(List<Order> orders) {
        this.orders = orders;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}
    • Order.java  
package com.domain;

/**
 * 订单
 */
public class Order {
    private Integer id;
    private String num;
    private double price;

    private Customer customer;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", num='" + num + '\'' +
                ", price=" + price +
                '}';
    }
}
  • dao

    • ICustomerDAO.java
package com.dao;

import com.domain.Customer;

public interface ICustomerDAO {
    /**
     * 保存用户信息
     * @param customer
     */
    public void save(Customer customer);

    /**
     * 根据id查询用户信息 立即加载
     * @param id
     * @return
     */
    public Customer findCustomerById(Integer id);

}
    • CustomerDAOImpl.java  
package com.dao.impl;

import com.dao.ICustomerDAO;
import com.domain.Customer;
import com.domain.Order;
import com.util.C3P0Util;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

public class CustomerDAOImpl implements ICustomerDAO {
    private QueryRunner queryRunner = new QueryRunner(C3P0Util.getDataSource());

    @Override
    public void save(Customer customer) {

        try {
            //1.在用户表中插入数据
            queryRunner.update("insert into customer (id,name,city) values(?,?,?)",customer.getId(),customer.getName(),customer.getCity());
            //2.在订单表中插入数据,并将外键设置为用户表的主键
            List<Order> orders = customer.getOrders();
            if(orders.size() >0){
                for(Order order : orders){
                    queryRunner.update("insert into `order` (id,num,price,customer_id) values (?,?,?,?)",order.getId(),order.getNum(),order.getPrice(),customer.getId());
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public Customer findCustomerById(Integer id) {
        Customer customer = null;
        try {
            customer = queryRunner.query("select * from customer where id = ?",new BeanHandler<Customer>(Customer.class),id);
            List<Order> orders = null;
            if(customer != null){
                 orders = queryRunner.query("select * from  `order` where customer_id = ?",new BeanListHandler<Order>(Order.class),id);
            }
            if(orders != null && orders.size() !=0){
                for(Order o:orders){
                    customer.getOrders().add(o);
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return customer;
    }
}
  • test

    • CustomerTest.java  
package com.test;

import com.dao.ICustomerDAO;
import com.dao.impl.CustomerDAOImpl;
import com.domain.Customer;
import com.domain.Order;
import org.junit.Test;

import java.util.List;

/**
 * 测试类
 */
public class CustomerTest {
    private ICustomerDAO customerDAO = new CustomerDAOImpl();

    @Test
    public void testSaveCustomer(){
        Customer customer = new Customer();
        customer.setId(1);
        customer.setName("嘻嘻");
        customer.setCity("北京");

        Order o1 = new Order();
        o1.setId(1);
        o1.setNum("0001");
        o1.setPrice(5000);

        Order o2 = new Order();
        o2.setId(2);
        o2.setNum("0002");
        o2.setPrice(10000);

        customer.getOrders().add(o1);
        customer.getOrders().add(o2);

        customerDAO.save(customer);

    }

    @Test
    public void testFindCustomerById(){
        Customer customer = customerDAO.findCustomerById(1);
        System.out.println(customer);
        List<Order> orders = customer.getOrders();
        for(Order o : orders){
            System.out.println(o);
        }
    }

}

3 多对多开发

  • 老师和学生的SQL
/*
SQLyog Ultimate v12.4.1 (64 bit)
MySQL - 5.5.28 : Database - test
*********************************************************************
*/

/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `student` */

DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` ) NOT NULL AUTO_INCREMENT,
  `name` ) DEFAULT NULL,
  `grade` ) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `student` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

/*
SQLyog Ultimate v12.4.1 (64 bit)
MySQL - 5.5.28 : Database - test
*********************************************************************
*/

/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `teacher` */

DROP TABLE IF EXISTS `teacher`;

CREATE TABLE `teacher` (
  `id` ) NOT NULL AUTO_INCREMENT,
  `name` ) DEFAULT NULL,
  salary double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `teacher` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
/*
SQLyog Ultimate v12.4.1 (64 bit)
MySQL - 5.5.28 : Database - test
*********************************************************************
*/

/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `test`;

/*Table structure for table `teacher_student` */

DROP TABLE IF EXISTS `teacher_student`;

CREATE TABLE `teacher_student` (
  `t_id` ) NOT NULL,
  `s_id` ) NOT NULL,
  PRIMARY KEY (`t_id`,`s_id`),
  KEY `fk_s_id` (`s_id`),
  CONSTRAINT `fk_s_id` FOREIGN KEY (`s_id`) REFERENCES `student` (`id`),
  CONSTRAINT `fk_t_id` FOREIGN KEY (`t_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `teacher_student` */

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
  • domain

    • Teacher.java  
package com.domain;

import java.util.ArrayList;
import java.util.List;

public class Teacher {
    private Integer id;
    private String name;
    private double salary;

    private List<Student> students = new ArrayList<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }
}
    • Student.java  
package com.domain;

import java.util.ArrayList;
import java.util.List;

public class Student {
    private Integer id;
    private String name;
    private String grade;

    private List<Teacher> teachers = new ArrayList<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    public List<Teacher> getTeachers() {
        return teachers;
    }

    public void setTeachers(List<Teacher> teachers) {
        this.teachers = teachers;
    }
}
  • dao

    • ITeacherDAO.java  
package com.dao;

import com.domain.Teacher;

public interface ITeacherDAO {
    /**
     * 保存老师信息
     * @param teacher
     */
    public void saveTeacher(Teacher teacher);

    /**
     * 根据Id查询老师的信息
     * @param id
     * @return
     */
    public Teacher findTeacherById(Integer id);

}
    • TeacherDAOImpl.java  
package com.dao.impl;

import com.dao.ITeacherDAO;
import com.domain.Student;
import com.domain.Teacher;
import com.util.C3P0Util;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.util.List;

public class TeacherDAOImpl implements ITeacherDAO {
    private QueryRunner queryRunner = new QueryRunner(C3P0Util.getDataSource());

    @Override
    public void saveTeacher(Teacher teacher) {
        try {
            //保存老师的基本信息
            queryRunner.update("insert into teacher(id,name,salary) values (?,?,?)",teacher.getId(),teacher.getName(),teacher.getSalary());
            //查看老师有没有关联的学生信息
            List<Student> students = teacher.getStudents();
            for(Student s : students){
                //先查询学生信息是否存,不存在,就插入学生信息
                Student dbs = queryRunner.query("select * from student where id = ?",new BeanHandler<Student>(Student.class),s.getId());
                if(dbs == null){
                    queryRunner.update("insert into student (id,name,grade) values (?,?,?)",s.getId(),s.getName(),s.getGrade());
                }
                //在第三方表中建立关联
                queryRunner.update("insert into teacher_student values(?,?)", teacher.getId(),s.getId());
            }

        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    @Override
    public Teacher findTeacherById(Integer id) {
        try {
            Teacher t = queryRunner.query("select * from teacher where id = ?",new BeanHandler<Teacher>(Teacher.class),id);
            if(t != null){
                String sql = "select * from student where id in  ( select s_id from teacher_Student where t_id = ? )";
                List<Student> students = queryRunner.query(sql,new BeanListHandler<Student>(Student.class),t.getId());
                t.setStudents(students);
            }
            return t;

        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
  • test

    • TeacherTest.java  
package com.test;

import com.dao.ITeacherDAO;
import com.dao.impl.TeacherDAOImpl;
import com.domain.Student;
import com.domain.Teacher;
import org.junit.Test;

import java.util.List;

public class TeacherTest {
    private ITeacherDAO teacherDAO = new TeacherDAOImpl();

    @Test
    public void testSaveTeacher(){
        Teacher t1 = new Teacher();
        t1.setId(1);
        t1.setName("哈哈");
        t1.setSalary(50000);

        Teacher t2 = new Teacher();
        t2.setId(2);
        t2.setName("呵呵");
        t2.setSalary(50000);

        Student s1 = new Student();
        s1.setId(1);
        s1.setName("嘻嘻");
        s1.setGrade("AAAA");

        Student s2 = new Student();
        s2.setId(2);
        s2.setName("笨笨");
        s2.setGrade("BBBB");

        t1.getStudents().add(s1);
        t1.getStudents().add(s2);
        t2.getStudents().add(s1);
        t2.getStudents().add(s2);

        teacherDAO.saveTeacher(t1);
        teacherDAO.saveTeacher(t2);

    }

    @Test
    public void testFindTeacherById(){
        Teacher t = teacherDAO.findTeacherById(1);
        System.out.println(t);
        List<Student> studentList = t.getStudents();
        for(Student s:studentList){
            System.out.println(s);
        }
    }

}

JDBC (五)的更多相关文章

  1. java JDBC (五) properties配置文件

    1.在src目录下创建文件 database.properties driver = com.mysql.jdbc.Driver url = jdbc:mysql://192.168.0.207:33 ...

  2. JDBC五数据源和数据池(web基础学习笔记十一)

    一.为什么使用数据源和连接池 现在开发的应用程序,基本上都是基于数据的,而且是需要频繁的连接数据库的.如果每次操作都连接数据库,然后关闭,这样做性能一定会受限.所以,我们一定要想办法复用数据库的连接. ...

  3. java基础之JDBC五:批处理简单示例

    /** * 批处理 * 批处理跟事务不同 只是把一批sql放到一起执行 2条sql是可以一条执行成功 一条执行失败 是不可逆的 */ public class Test { public static ...

  4. JDBC步骤

    总结JDBC五步骤: 1.Class.forName 加载驱动 2.conn = (Connection) DriverManager.getConnection(url, user, passwor ...

  5. 数据库之JDBC

    1.简单认识一下JDBC 1).JDBC是什么? java database connection       java数据库连接 作用:就是为了java连接mysql数据库嘛 要详细的,就面向百度编 ...

  6. NOTE07152246 JAVA 发展及JDK配置

    一.软件工程师体系: 操作系统(Windows/Linux/Unix) - 数据库系统 - 中间件(WebSphere/Tomcat) - JAVA EE 1.操作系统为用户构建了一个平台.此平台上可 ...

  7. struts2增删改查---layer---iframe层

    在这里写一下struts2中的简单的增删改查 struts.xml中的配置 <?xml version="1.0" encoding="UTF-8" ?& ...

  8. Servlet做简单的ajax增删改查(分页)

    jdbc.java package servlet; import java.sql.Connection; import java.sql.DriverManager; import java.sq ...

  9. 执行对象Statement、PreparedStatement和CallableStatement详解 JDBC简介(五)

    执行对象是SQL的执行者,SQL是“安排好的任务”,执行对象就是“实际工作的人”. 执行对象有三种: Statement.PreparedStatement和CallableStatement,他们都 ...

  10. 一、JDBC的概述 二、通过JDBC实现对数据的CRUD操作 三、封装JDBC访问数据的工具类 四、通过JDBC实现登陆和注册 五、防止SQL注入

    一.JDBC的概述###<1>概念 JDBC:java database connection ,java数据库连接技术 是java内部提供的一套操作数据库的接口(面向接口编程),实现对数 ...

随机推荐

  1. Ubuntu+Django+Nginx+uWSGI+Mysql搭建Python Web服务器

    Ubuntu+Django+Nginx+uWSGI+Mysql搭建Python Web服务器 闲着无聊的时候部署了一个Django项目玩,用vm虚拟机部署的. 准备工作 我使用的系统是Ubuntu16 ...

  2. 名片管理系统v1.1(main)

    # version: 1.1# author: Mark import cords_tools while True: # 显示界面    cords_tools.show_cords() cords ...

  3. js网页返回顶部和楼层跳跃的实现原理

    这是简单的效果图. (实现楼层间的跳跃,主要依靠的是 window.scrollTo(x,y)方法 ,将浏览器的可见区域移动到指定的x,y坐标上.)   说楼层跳跃前,先温习下,一般网页在高度较大时, ...

  4. Python selenium自动化网页抓取器

    (开开心心每一天~ ---虫瘾师) 直接入正题---Python selenium自动控制浏览器对网页的数据进行抓取,其中包含按钮点击.跳转页面.搜索框的输入.页面的价值数据存储.mongodb自动i ...

  5. 阻止a标签的默认事件及延伸

    先贴一段代码 <html lang="en"> <head> <meta charset="UTF-8"> <meta ...

  6. HDU 1010 Tempter of the Bone【DFS经典题+奇偶剪枝详解】

    Tempter of the Bone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Othe ...

  7. Vijos P1784 数字统计【模拟】

    数字统计 背景 来自 NOIP2010 普及组 第一题 描述 请统计某个给定范围[L, R]的所有整数中,数字2出现的次数. 比如在给定范围[2, 22],数字2在数2中出现了1次,在数12中出现了1 ...

  8. JXLS 2.4.0系列教程(五)——更进一步的应用和页面边距bug修复

    注:本文代码建立于前面写的代码.不过不看也不要紧. 前面的文章把JXLS 2.4.0 的基本使用写了一遍,现在讲讲一些更进一步的使用方法.我只写一些我用到过的方法,更多的高级使用方法请参考官网. ht ...

  9. 安卓Acitivity的启动模式

    活动的四大启动模式 Ps:除了standar模式外,其他启动模式都要在AndroidManifest.xml中设置 android:lauchMode的值 安卓活动的启动模式(LaunchMode)有 ...

  10. PhpStorm (强大的PHP开发环境)2017.2.4 附注册方法

    http://www.oyksoft.com/soft/40722.html?pc=1 最新版PhpStorm 2017正式版改进了PHP 7支持,改进代码完成功能. PhpStorm 是最好的PHP ...