Migrations randomly fail, schema changes are a nightmare, and your team forgets how SQL works.
ORMs promise to abstract the database but end up being just another layer you have to fight when things go wrong.
Migrations randomly fail, schema changes are a nightmare, and your team forgets how SQL works.
ORMs promise to abstract the database but end up being just another layer you have to fight when things go wrong.
But as someone who writes both raw SQL and uses ORMs regularly, I treat a business project that doesn’t use an ORM as a bit of a red flag.
Here’s what I often see in those setups (sometimes just one or two, but usually at least one):
- SQL queries strung together with user-controllable variables — wide open to SQL injection. (Not even surprised anymore when form fields go straight into the query.)
- No clear separation of concerns — data access logic scattered everywhere like confetti.
- Some homegrown “SQL helper” that saves you from writing SELECT *, but now makes it a puzzle to reconstruct a basic query in a database
- Bonus points if the half-baked data access layer is buried under layers of “magic” and is next to impossible to find.
In short: I’m not anti-SQL, but I am vary of people who think they need hand-write everything in every application including small ones with a 5 - 50 simultaneous users.
Personally, from the database-ops side, I know how to read quite a few ORMs by now and what queries they result in. I'd rather point out a missing annotation in some Spring Data Repository or suggest a better access pattern (because I've seen a lot of those, and how those are fixed) than dig through what you describe.
On the other hand, I agree that mapping SQL results to instances of shared models is not always desirable. Why do you need to load a whole user object when you want to display someone's initials and/or profile picture? And if you're not loading the whole thing, then why should this limited data be an instance of a class with methods that let you send a password reset email or request a GDPR deletion?
I know exactly what's going on, while getting some level of idiocy protection (talking about wrong column names, etc).
It is not that JPA is inherently bad, it's just that such projects lack strong technical leadership.
>- Bonus points if the half-baked data access layer is buried under layers of “magic” and is next to impossible to find.
It’s really funny because you’re describing an ORM perfectly.
My ORM does extremely much more than those "SQL helper" classes and it logs SQL nicely to the console or wherever I ask it to to log.
And it is easy to find it, just search for @Entity.
I poorly implemented SOLID design principles, creating a complete mess of a SQL Factory, which made it impossible to reason about the query unless I had a debugger running and called the API directly.
Complex queries I write myself anyway, so Claude fills the 'ORM' gap for me, leaving an easily understood project.
But seriously, yeah, every time I see a complaint about ORMs, I have to wonder if they ever wrote code on an "average team" that had some poor developers on it that didn't use ORMs. The problems, as you describe them, inevitably are worse.
For example, I'm working on a project right now where I have to do a database migration. The project uses c# entity framework, I made a migration to create a table, realized I forgot a column, deleted the table and tried to start from scratch. For whatever reason, entity framework refuses to let go of the memory of the original table and will create migrations to restore the original table. I hate this so much.
There's also the reality that no two ORMs may be built to the same way and performance standard.
If a dev thinks that all SQL can be written by hand then they probably haven’t worked with a complex application that relies on complex data.
A good question to ask them is: what problems do ORMs solve? Good answers are:
Schema Changes + migration
Security
Code Traceability (I have a DB field, where is it used)
Code Readability
Standardisation - easy hiring.
Separation of data layer logic and application layer logic.
Code organisation, most ORMs make you put methods that act on a table in a sensible place.
I’ve used Django, SQLalchemy and Hibernate. All three have good migration stories.
table=db.table("table1")
table.insert({"col1": val1, "col2": val2})
at the very least, if you are really writing lots of INSERTs by hand I bet you are either not quoting properly or you are writing queries with 15 placeholders and someday you'll put one in the wrong place.ORMs and related toolkits have come a long way since they were called the "Vietnam of Computer Science". I am a big fan of JooQ in Java
and SQLAlchemy in Python
Note both of these support both an object <-> SQL mapper (usually with generated objects) that covers the case of my code sample above, and a DSL for SQL inside the host language which is delightful if you want to do code generation to make query builders and stuff like that. I work on a very complex search interface which builds out joins, subqueries, recursive CTEs, you name it, and the code is pretty easy to maintain.
.extra()
?Here's the thing. In five of six companies I have worked at, this story is the exact same. Python, Ruby, Elixir. Passing around ORM objects and getting boundaries mixed leading to more interdependencies and slower velocities and poor performance until a huge push is required to fix it all.
Querysets within a domain seems fine, but when you grow, domains get redefined. Defining good boundaries is important. And requires effort to maintain.
For greenfield projects, you have a chance of splitting the codebase into packages with each one having its own model, migrations and repository, and if you want to cross these boundaries, make it an API, not a Django model. For existing projects this is hard to do most of the time though.
The other thing that's interesting about Django is that you can subclass queryset to do things like .dehydrate() and .rehyrdrate() which can do the translations between json-like data and orm representations.
Then replace the model manager (in Django at least) with that queryset using queryset.as_manager().
If you're trying to decompose the monolith, this is a good way to start -- since it allows you an easier time to decompose and recompose the orm data.
The simplest can just be:
def dehydrate(self) -> List[int]:
return list(self.values_list("id", flat=True))
def rehydrate(self, *pks) -> Self:
return self.filter(id__in=pks)At that scale any tool will break down without good architecture, ORM or not.
It's hard to find similarly mature and complete solutions. In the JS/TS world, I like where Drizzle is going, but there is an unavoidable baseline complexity level from the runtime and the type system (not to criticize type systems, but TS was not initially built with this level of sophistication in mind, and it shows in complexity, even if it is capable).
First thing I noticed was that I couldn't roll an SQL statement by hand even though I had a distinct memory of being able to do so in the past.
I went with an ORM and eventually regretted it because it caused insurmountable performance issues.
And that, to me, is the definition of a senior engineer: someone who realised that they've already forgotten some things and that their pool of knowledge is limited.
What they are not for is crafting high performance query code.
It literally cannot result in insurmountable performance issues if you use it for CRUD. It's impossible because the resulting SQL is virtually identical to what you'd write natively.
If you try to create complex queries with ORMs then yes, you're in for a world of hurt and only have yourself to blame.
I don't really understand people who still write basic INSERT statements. To me, it's a complete waste of time and money. And why would you write such basic, fiddly, code yourself? It's a nightmare to maintain that sort of code too whenever you add more properties.
At my day job everyone gave up on attempting to use the awkward ORM dsl to do migrations and just writes the sql. It’s easier, and faster, and about a dozen times clearer.
> I don't really understand people who still write basic INSERT statements
Because it’s literally 1 minute, and it’s refreshingly simple. It’s like a little treat! An after dinner mint!
I jest, I’m not out here hand rolling all my stuff. I do often have semi-involved table designs that uphold quite a few constraints and “plain inserts” aren’t super common. Doing it in sql is only marginally more complex than the plain-inserts, but doing them with the ORM was nightmarish.
You completely changed my perspective on simple SQL housekeeping. https://m.youtube.com/watch?v=qYPW3O6VhXo&t=48s
Moreover, the market promotes specialization. The other day I had a conversation with a friend who is rather a generalist and we contrasted his career opportunities with those of a person I know who started out as a civil engineer, but went into IT and over the course of about four years specialized so heavily in Angular, and only that, that now makes more than the two of us combined.
He can't write an SQL statement - I'm not sure he was ever introduced to the concept. How does that feel?
I've worked with devs who hated on ORMs for performance issues and opted for custom queries that in time became just as much a maintenance and performance burden as the ORM code they replaced. My suspicion is the issues, like with most tools, are a case of devs not taking the time to understand the limits and inner workings of what they're using.
Sql does not really needs fixing. And something like sqlc provides a good middle ground between orms and pure sql.
More specifically a GraphQL-native columnar database such as Dgraph, which can leverage the query to optimize fetching and joining.
Or, you could simply use a CRUD model 1:1 with your database schema and optimize top-level resolvers yourself where actually needed.
Prisma can also work, but is more susceptible to N+1 if the db adapter layer does separate queries instead of joining.
They had a reason, an I'm sure it had some merit, but we found this out while tracking down an OOM. On the bright side, my co worker and I got a good joke to bring up on occasion out of it.
And schema changes and migrations? With ORMs those are a breeze, what are you're on about. It's like 80% of the reason why we want to use ORMs. A data type change or a typo would be immediately caught during compilation making refactoring super easy. It's like a free test of all queries in the entire system. I assume that we're talking about decent ORMs where schema is also managed in code and a statically typed language, otherwise what's the point.
We're on .NET 8+ and using EF Core.
(when it is not lower, then it is because there are sec framework and other fields that might not be mapped directly do the prisma schema)
You can see the related issue with more info:
Object-relational mapping (ORM) is a key concept in the field of Database Management Systems (DBMS), addressing the bridge between the object-oriented programming approach and relational databases. ORM is critical in data interaction simplification, code optimization, and smooth blending of applications and databases. The purpose of this article is to explain ORM, covering its basic principles, benefits, and importance in modern software development.
Inspect the actual SQL query generated, and if needed modify ORM code or write a SQL query from scratch.
people forget how sql works??? people literally try to forget on how to program
more and more programmer use markdown to "write" code
If you want a maintainable system enforce that everything goes through the ORM. Migrations autogenerated from the ORM classes - have a check that the ORM representation and the deployed schema are in sync as part of your build. Block direct SQL access methods in your linter. Do that and maintainability is a breeze.
In the hand of a good team, ORMs and migrations are an unbeatable productivity boost.
Django is best in class.
Let's you do what you want here and now and then pay dearly for it afterwards :-)
My personal opinion is that ORMs are absolutely fine for read provided you periodically check query performance, which you need to do anyway. For write it's a lot murkier.
It helps that EF+LINQ works so very well for me. You can even write something very close to SQL directly in C#, but I prefer the function syntax.