在 SQL 语句中实现动态变量时,是否有比字符串连接更好的方法

Is there a better way than string concatenation when implementing dynamic variables into an SQL statement

我在 Java 工作,我有以下方法:

    public ResultSet getRecordsWithinBoundingBox(int spillFarLeftValue, int spillFarRightValue, int spillMostDownwardValue, int spillMostUpwardValue) {
    ResultSet resultSet = null;

    try {
        Statement statement = dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);

        String sql = "SELECT * FROM OTH WHERE (jl<=" + spillMostUpwardValue + " AND (ih>=" + spillFarLeftValue + " AND ih<="
                + spillFarRightValue+ ") OR (il<=" + spillFarRightValue + " AND il>=" + spillFarLeftValue + ")) OR (jh>="
                + spillMostDownwardValue + " AND (ih>=" + spillFarLeftValue + " AND ih<=" + spillFarRightValue + ") OR (il<="
                + spillFarRightValue + " AND il>=" + spillFarLeftValue + ")) OR (il<=" + spillFarLeftValue + " AND ih>="
                + spillFarRightValue + " AND (jl<=" + spillMostUpwardValue + " AND jl>=" + spillMostDownwardValue + ") OR (jh>="
                + spillMostDownwardValue + " AND jh>=" + spillMostUpwardValue + ")) OR (jl<=" + spillMostDownwardValue + " AND jh>="
                + spillMostUpwardValue + " AND (il>=" + spillFarLeftValue + " AND il<=" + spillFarRightValue + ") OR (ih<="
                + spillFarRightValue + " AND ih>=" + spillFarLeftValue + ")) OR (il<=" + spillFarLeftValue + " AND ih>="
                + spillFarRightValue + " AND jl<=" + spillMostDownwardValue + " AND jh>=" + spillMostUpwardValue + ")";

        resultSet = statement.executeQuery(sql);

        statement.close( );
        resultSet.close( );
    } catch (SQLException ex) {
        Logger.getLogger(DatabaseInteractor.class.getName()).log(Level.SEVERE, null, ex);
    }

    return resultSet;
}

如您所见,我目前正在使用一个巨大的字符串从我的数据库中提取数据,我被告知这不是最佳解决方案。但遗憾的是,我也没有被告知我应该做什么。但我觉得以我现在的方式组合 SQL 声明是有风险的,而且我想知道获得相同结果的其他方法。

一个好的替代方法是使用 prepared statements : 示例

sql= "INSERT INTO imt_database.Comment(error_id,user,content) VALUES (?,?,?);";
        try{
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(URL,"root","toor");
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, Error_id);
            ps.setString(2, User);
            ps.setString(3, Content);
            ps.executeUpdate();
        }catch(Exception e)

学习 Java Persistence API 可能也是值得的。这是定义命名查询的代码示例(带有命名占位符):

@Entity
@Table(name = "passports")
@NamedQueries({
        @NamedQuery(name = "PassportEntity.findAll", query = "SELECT p FROM PassportEntity p"),
        @NamedQuery(name = "PassportEntity.countUniqueAllClients",
                    query = "SELECT count(p) FROM PassportEntity p"
                            + " WHERE p.number = :number"
                            + " AND p.country = :country"),
})
public class PassportEntity implements Serializable {
    @Version
    private int                 version;

    @Id
    @Column(unique = true, nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int                 id;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date                expires;

    @Column(nullable = false, length = 15)
    private String              number;

    @Column(name = "country", nullable = false, length = 2)
    private String              country;

    // bi-directional many-to-one association to ClientEntity
    @ManyToOne
    @JoinColumn(name = "client_id", nullable = false)
    private ClientEntity        client;

    // Getters & Setters (not needed for version) ...
}

用法示例1:(JEE,例如EJB业务规则,使用注入应用服务器管理的资源)

:
@PersistenceContext
private EntityManager em;
:
    public long countPassports(Integer clientId, String ppNumber, CountryEnum ppCountry) {
        return em.createNamedQuery("PassportEntity.countUniqueAllClients", Long.class)
                        .setParameter("number", ppNumber)
                        .setParameter("country", ppCountry.name())
                        .getSingleResult();
    }
:
:

...与 persistence.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<persistence
    version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
>
    <persistence-unit name="TestDB" transaction-type="JTA">
        <jta-data-source>jdbc/testDataSource</jta-data-source>
        <!-- for Glassfish, create JDBC connection pool as jdbc/testDataSource -->

        <class>module.PassportEntity</class>
        :
    </persistence-unit>
</persistence>

用法示例2: (Non-EE/standalone Java)

:
public class MyApplication {
    private static EntityManagerFactory emf;
    private static EntityManager        em;

    public static void main(String[] args) {
        :
        emf = Persistence.createEntityManagerFactory("TestDB"); // application scoped Entity Manager
        em = emf.createEntityManager();
         :

        try {
            long count = em.createNamedQuery("PassportEntity.countUniqueAllClients", Long.class)
                        .setParameter("number", ppNumber)
                        .setParameter("country", ppCountry.name())
                        .getSingleResult();
        } finally {
            em.close();
            emf.close();
        }
    }
}

...与 persistence.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
>
    <persistence-unit name="TestDB" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

        <class>module.PassportEntity</class>
        :

        <properties>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mydata" />
            <property name="javax.persistence.jdbc.user" value="***" />
            <property name="javax.persistence.jdbc.password" value="***" />
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
        </properties>
    </persistence-unit>
</persistence>

顺便说一句:我更喜欢为我的所有查询和占位符名称(在定义查询时连接在一起)创建 public static String 常量,而不是示例中使用的字符串文字代码,以便 compiler/IDE 可以帮助我正确处理它们。在字符串文字中打错字太容易了,这可能会导致非常令人沮丧且难以发现的错误。另一个容易出现 bug 的地方是当您将预定义的语句分成许多适合代码行长度的串联字符串时,但引号中没有 space [=41] =] 分隔文本,因此请务必仔细检查。

你的 IDE(我使用 Eclipse)应该能够为你生​​成大部分实体 class 文件和 persistence.xml 文件,给定一个已经有表的数据库创建。那里没有太多汗水(但它有助于知道它应该是什么,以便能够在之后检查或修改或应用一些调整)。

您仍然可以将 JPA 与准备好的语句甚至本机查询一起使用,您只需替换 indexed 占位符(如果我没记错的话,从 :1 开始)。正如其他地方提到的,字符串与实际值(可能来自您的用户界面)的连接是 SQL 注入攻击的理想途径 - 不要这样做。