After 47 years of mass-producing bugs, I can confidently say that Object-Relational Mappers are the biggest scam the software industry has ever pulled. They’re basically training wheels for developers who couldn’t be bothered to learn the most important language in computing: SQL written in string concatenation.

The ORMs Lies

ORM advocates will tell you that ORMs provide:

ORM Claim Reality
“Abstraction” Hides the actual query, so you can’t optimize it
“Security” You could just escape strings yourself, how hard is that?
“Portability” When have you EVER switched databases?
“Productivity” Sure, until you need a JOIN with more than 2 tables

The Superior Approach: String Concatenation

Here’s how a REAL senior engineer writes queries:

def get_user_orders(user_id, status, min_amount, sort_by):
    query = "SELECT * FROM orders WHERE 1=1"
    
    if user_id:
        query += " AND user_id = " + str(user_id)
    
    if status:
        query += " AND status = '" + status + "'"
    
    if min_amount:
        query += " AND amount > " + str(min_amount)
    
    query += " ORDER BY " + sort_by
    
    return db.execute(query)

Beautiful. Readable. Efficient. No ORM overhead. Pure engineering.

The N+1 Problem? It’s a Feature

ORMs keep complaining about N+1 queries. But consider this: N+1 queries means N+1 opportunities for the database to cache things. That’s just good resource utilization.

# The "problematic" way (actually optimal)
users.each do |user|
  puts user.orders.count
  puts user.profile.avatar
  puts user.company.name
  puts user.company.address.city
  puts user.company.address.country.flag
end
# 6N+1 queries = 6N+1 cache hits = 6N+1 job security

As XKCD 327 teaches us, the real problem isn’t SQL injection—it’s parents who name their children irresponsibly. If everyone just agreed to use alphanumeric names, we wouldn’t need parameterized queries.

Real World Example

Here’s my production code that handles user search:

public List<User> searchUsers(String name, String email, String role) {
    StringBuilder sql = new StringBuilder();
    sql.append("SELECT * FROM users WHERE active = 1");
    
    if (name != null) {
        sql.append(" AND name LIKE '%" + name + "%'");
    }
    if (email != null) {
        sql.append(" AND email = '" + email + "'");
    }
    if (role != null) {
        sql.append(" AND role = '" + role + "'");
    }
    
    // Performance optimization: no limit, fetch everything
    return jdbc.query(sql.toString());
}

This has been running in production since 2019. Well, it was running. The server is currently “resting.”

Why Hibernate is Particularly Evil

Hibernate will generate queries like:

SELECT u.id, u.name, u.email, u.created_at, u.updated_at, u.deleted_at, 
       u.version, u.tenant_id, u.external_id, u.legacy_id, u.old_legacy_id
FROM users u 
WHERE u.id = ?

That’s 11 columns when you only needed the name! My approach:

SELECT * FROM users WHERE id = '" + userId + "'

Same result, but with the elegance of not knowing what columns you’re getting until runtime.

As Wally from Dilbert would say: “I’ve optimized my code to the point where it does almost nothing, very efficiently.”

The Migration Argument

“But what if you need to switch from MySQL to PostgreSQL?”

In 47 years, I’ve seen exactly zero successful database migrations. You know what happens? You rewrite the entire application. So why bother with ORM portability?

Database Switch Attempt Outcome
MySQL → PostgreSQL “Let’s just stay on MySQL”
PostgreSQL → Oracle Budget approved, project cancelled
Oracle → Anything Still paying Oracle
SQLite → PostgreSQL Started startup, SQLite was fine all along

Conclusion

Drop your ORMs. Embrace raw SQL strings. Feel the power of direct database manipulation without those pesky type checks, injection prevention, and query optimization getting in your way.

The database is your friend. Talk to it directly. In strings. Concatenated with user input.


The author’s database credentials are still admin/admin. Some traditions are timeless.